clinkx 0.2.2 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -1
- package/clinkx-workflows/dist/clink-client.js +35 -10
- package/clinkx-workflows/dist/clink-client.js.map +1 -1
- package/clinkx-workflows/dist/config.d.ts +10 -0
- package/clinkx-workflows/dist/config.js +18 -0
- package/clinkx-workflows/dist/config.js.map +1 -1
- package/clinkx-workflows/dist/council-expander.d.ts +43 -0
- package/clinkx-workflows/dist/council-expander.js +330 -0
- package/clinkx-workflows/dist/council-expander.js.map +1 -0
- package/clinkx-workflows/dist/index.d.ts +14 -0
- package/clinkx-workflows/dist/index.js +264 -16
- package/clinkx-workflows/dist/index.js.map +1 -1
- package/clinkx-workflows/dist/server.d.ts +8 -1
- package/clinkx-workflows/dist/server.js +87 -1
- package/clinkx-workflows/dist/server.js.map +1 -1
- package/clinkx-workflows/dist/shutdown.d.ts +5 -9
- package/clinkx-workflows/dist/shutdown.js +6 -23
- package/clinkx-workflows/dist/shutdown.js.map +1 -1
- package/clinkx-workflows/dist/state.d.ts +13 -0
- package/clinkx-workflows/dist/state.js +198 -2
- package/clinkx-workflows/dist/state.js.map +1 -1
- package/clinkx-workflows/dist/transport.js +24 -8
- package/clinkx-workflows/dist/transport.js.map +1 -1
- package/clinkx-workflows/dist/workflow-tools.d.ts +14 -1
- package/clinkx-workflows/dist/workflow-tools.js +173 -12
- package/clinkx-workflows/dist/workflow-tools.js.map +1 -1
- package/clinkx-workflows/dist/workflows/index.d.ts +4 -3
- package/clinkx-workflows/dist/workflows/index.js +4 -3
- package/clinkx-workflows/dist/workflows/index.js.map +1 -1
- package/clinkx-workflows/templates/council/answer-chairman.txt +49 -0
- package/clinkx-workflows/templates/council/answer-cross-reviewer.txt +37 -0
- package/clinkx-workflows/templates/council/answer-specialist.txt +40 -0
- package/clinkx-workflows/templates/council/chairman.txt +59 -0
- package/clinkx-workflows/templates/council/code-review-chairman.txt +57 -0
- package/clinkx-workflows/templates/council/code-review-cross-reviewer.txt +43 -0
- package/clinkx-workflows/templates/council/code-review-reviewer.txt +39 -0
- package/clinkx-workflows/templates/council/debug-chairman.txt +57 -0
- package/clinkx-workflows/templates/council/debug-cross-reviewer.txt +41 -0
- package/clinkx-workflows/templates/council/debug-debugger.txt +46 -0
- package/clinkx-workflows/templates/council/discover-analyst.txt +38 -0
- package/clinkx-workflows/templates/council/discover-chairman.txt +51 -0
- package/clinkx-workflows/templates/council/discover-cross-reviewer.txt +40 -0
- package/clinkx-workflows/templates/council/member.txt +48 -0
- package/clinkx-workflows/templates/council/reviewer.txt +57 -0
- package/clinkx-workflows/workflows/council-answer.yaml +147 -0
- package/clinkx-workflows/workflows/council-code-review.yaml +152 -0
- package/clinkx-workflows/workflows/council-debug.yaml +152 -0
- package/clinkx-workflows/workflows/council-default.yaml +147 -0
- package/clinkx-workflows/workflows/council-discover.yaml +147 -0
- package/conf/prompts/json-codereviewer.txt +1 -0
- package/conf/prompts/json.txt +1 -0
- package/dist/allowed-roots.d.ts +12 -0
- package/dist/allowed-roots.js +94 -0
- package/dist/allowed-roots.js.map +1 -0
- package/dist/context-builder/anchors.d.ts +11 -0
- package/dist/context-builder/anchors.js +19 -0
- package/dist/context-builder/anchors.js.map +1 -0
- package/dist/context-builder/bundle.d.ts +76 -0
- package/dist/context-builder/bundle.js +3573 -0
- package/dist/context-builder/bundle.js.map +1 -0
- package/dist/context-builder/codemap.d.ts +17 -0
- package/dist/context-builder/codemap.js +301 -0
- package/dist/context-builder/codemap.js.map +1 -0
- package/dist/context-builder/config.d.ts +60 -0
- package/dist/context-builder/config.js +204 -0
- package/dist/context-builder/config.js.map +1 -0
- package/dist/context-builder/delegate-packet.d.ts +182 -0
- package/dist/context-builder/delegate-packet.js +898 -0
- package/dist/context-builder/delegate-packet.js.map +1 -0
- package/dist/context-builder/delegate-reconstruction.d.ts +13 -0
- package/dist/context-builder/delegate-reconstruction.js +220 -0
- package/dist/context-builder/delegate-reconstruction.js.map +1 -0
- package/dist/context-builder/delegate-validation.d.ts +18 -0
- package/dist/context-builder/delegate-validation.js +367 -0
- package/dist/context-builder/delegate-validation.js.map +1 -0
- package/dist/context-builder/delegate.d.ts +24 -0
- package/dist/context-builder/delegate.js +133 -0
- package/dist/context-builder/delegate.js.map +1 -0
- package/dist/context-builder/diagnostics.d.ts +27 -0
- package/dist/context-builder/diagnostics.js +74 -0
- package/dist/context-builder/diagnostics.js.map +1 -0
- package/dist/context-builder/discovery.d.ts +118 -0
- package/dist/context-builder/discovery.js +1829 -0
- package/dist/context-builder/discovery.js.map +1 -0
- package/dist/context-builder/globs.d.ts +14 -0
- package/dist/context-builder/globs.js +175 -0
- package/dist/context-builder/globs.js.map +1 -0
- package/dist/context-builder/handler.d.ts +17 -0
- package/dist/context-builder/handler.js +3848 -0
- package/dist/context-builder/handler.js.map +1 -0
- package/dist/context-builder/path-signals.d.ts +7 -0
- package/dist/context-builder/path-signals.js +66 -0
- package/dist/context-builder/path-signals.js.map +1 -0
- package/dist/context-builder/paths.d.ts +30 -0
- package/dist/context-builder/paths.js +153 -0
- package/dist/context-builder/paths.js.map +1 -0
- package/dist/context-builder/progress.d.ts +15 -0
- package/dist/context-builder/progress.js +70 -0
- package/dist/context-builder/progress.js.map +1 -0
- package/dist/context-builder/receipt.d.ts +89 -0
- package/dist/context-builder/receipt.js +169 -0
- package/dist/context-builder/receipt.js.map +1 -0
- package/dist/context-builder/run-id.d.ts +7 -0
- package/dist/context-builder/run-id.js +31 -0
- package/dist/context-builder/run-id.js.map +1 -0
- package/dist/context-builder/schema.d.ts +431 -0
- package/dist/context-builder/schema.js +293 -0
- package/dist/context-builder/schema.js.map +1 -0
- package/dist/context-builder/secrets.d.ts +13 -0
- package/dist/context-builder/secrets.js +222 -0
- package/dist/context-builder/secrets.js.map +1 -0
- package/dist/context-builder/selection.d.ts +52 -0
- package/dist/context-builder/selection.js +1649 -0
- package/dist/context-builder/selection.js.map +1 -0
- package/dist/context-builder/status.d.ts +45 -0
- package/dist/context-builder/status.js +105 -0
- package/dist/context-builder/status.js.map +1 -0
- package/dist/context-builder/storage.d.ts +85 -0
- package/dist/context-builder/storage.js +746 -0
- package/dist/context-builder/storage.js.map +1 -0
- package/dist/context-builder/token-estimate.d.ts +8 -0
- package/dist/context-builder/token-estimate.js +17 -0
- package/dist/context-builder/token-estimate.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +246 -14
- package/dist/index.js.map +1 -1
- package/dist/paths.d.ts +8 -0
- package/dist/paths.js +31 -43
- package/dist/paths.js.map +1 -1
- package/dist/pipeline.d.ts +48 -4
- package/dist/pipeline.js +164 -44
- package/dist/pipeline.js.map +1 -1
- package/dist/runner.d.ts +2 -2
- package/dist/runner.js +24 -3
- package/dist/runner.js.map +1 -1
- package/dist/server.d.ts +1 -0
- package/dist/server.js +42 -11
- package/dist/server.js.map +1 -1
- package/package.json +6 -2
- package/clinkx-workflows/dist/workflows/council-default.d.ts +0 -123
- package/clinkx-workflows/dist/workflows/council-default.js +0 -141
- package/clinkx-workflows/dist/workflows/council-default.js.map +0 -1
- package/dist/artifacts.d.ts +0 -9
- package/dist/artifacts.js +0 -24
- package/dist/artifacts.js.map +0 -1
|
@@ -0,0 +1,3573 @@
|
|
|
1
|
+
import { boundContextBuilderOmissions, boundContextBuilderPathList, boundContextBuilderWarnings, sanitizeDiagnosticText, } from "./diagnostics.js";
|
|
2
|
+
import { createContextBuilderReceipt, cloneContextBuilderDelegateReceiptMetadata, } from "./receipt.js";
|
|
3
|
+
import { assertNoHighConfidenceSecretsInTask, hasHighConfidenceSecret, } from "./secrets.js";
|
|
4
|
+
import { capEventsFromContextBuilderMetrics, reduceContextBuilderStatus, } from "./status.js";
|
|
5
|
+
import { writeContextBuilderArtifactsAndCommit, } from "./storage.js";
|
|
6
|
+
import { contextBuilderTokenCountingMethod, estimateContextBuilderTokens, } from "./token-estimate.js";
|
|
7
|
+
import { contextBuilderInternalCapsForManifest, } from "./config.js";
|
|
8
|
+
import { buildFileCodemap, remapCodemapToSourceCoordinates, renderCodemapJsonl, } from "./codemap.js";
|
|
9
|
+
export function renderContextBuilderBundle(input) {
|
|
10
|
+
assertNoHighConfidenceSecretsInTask(input.task);
|
|
11
|
+
const selected = input.selected_context.map(sanitizeSelectedFile);
|
|
12
|
+
const tree = input.tree.map(sanitizeTreeFile);
|
|
13
|
+
const search = input.search.map(sanitizeSearchRecord);
|
|
14
|
+
const slices = input.slices.map(sanitizeSliceRecord);
|
|
15
|
+
const presentationSelected = promoteTaskPresentationEvidence(filterTaskPresentationSelected(selected, input.task), input.task, slices);
|
|
16
|
+
const omissions = boundContextBuilderOmissions([...(input.omissions ?? [])], contextBuilderInternalCapsForManifest(input.internal_caps));
|
|
17
|
+
const warnings = boundContextBuilderWarnings([...(input.warnings ?? [])], contextBuilderInternalCapsForManifest(input.internal_caps));
|
|
18
|
+
const presentationOmissions = reconcileSelectedOmissions(filterTaskPresentationOmissions(omissions, input.task), presentationSelected);
|
|
19
|
+
const openQuestions = buildPresentationOpenQuestions(boundOpenQuestions(input.open_questions ?? []), presentationSelected, presentationOmissions);
|
|
20
|
+
const showRootLabels = input.roots.length > 1;
|
|
21
|
+
return [
|
|
22
|
+
"# Context Bundle",
|
|
23
|
+
"",
|
|
24
|
+
"## Original Task",
|
|
25
|
+
"",
|
|
26
|
+
input.task,
|
|
27
|
+
"",
|
|
28
|
+
"## How To Use This Bundle",
|
|
29
|
+
"",
|
|
30
|
+
"- Start with top must-read ranges, then verify the generated answer against the source evidence immediately below it.",
|
|
31
|
+
"- Inspect selected files, repository shape, and search evidence before editing.",
|
|
32
|
+
"- Treat remaining omissions and open questions as follow-up checks before editing.",
|
|
33
|
+
"",
|
|
34
|
+
"## Top Must-Read Ranges",
|
|
35
|
+
"",
|
|
36
|
+
renderTopMustReadRanges({
|
|
37
|
+
task: input.task,
|
|
38
|
+
selected: presentationSelected,
|
|
39
|
+
search,
|
|
40
|
+
showRootLabels,
|
|
41
|
+
}),
|
|
42
|
+
"",
|
|
43
|
+
"## Generated Answer",
|
|
44
|
+
"",
|
|
45
|
+
renderGeneratedAnswer({
|
|
46
|
+
task: input.task,
|
|
47
|
+
selected: presentationSelected,
|
|
48
|
+
omissions: presentationOmissions,
|
|
49
|
+
showRootLabels,
|
|
50
|
+
}),
|
|
51
|
+
"",
|
|
52
|
+
"### Source Evidence",
|
|
53
|
+
"",
|
|
54
|
+
renderGeneratedAnswerSourceEvidence({
|
|
55
|
+
task: input.task,
|
|
56
|
+
selected: presentationSelected,
|
|
57
|
+
slices,
|
|
58
|
+
showRootLabels,
|
|
59
|
+
}),
|
|
60
|
+
"",
|
|
61
|
+
"## Relationships And Risks",
|
|
62
|
+
"",
|
|
63
|
+
renderRelationshipsAndRisks({
|
|
64
|
+
task: input.task,
|
|
65
|
+
selected: presentationSelected,
|
|
66
|
+
omissions: presentationOmissions,
|
|
67
|
+
showRootLabels,
|
|
68
|
+
}),
|
|
69
|
+
"",
|
|
70
|
+
"## Selected Files",
|
|
71
|
+
"",
|
|
72
|
+
renderSelectedFilesList({
|
|
73
|
+
task: input.task,
|
|
74
|
+
selected: presentationSelected,
|
|
75
|
+
search,
|
|
76
|
+
showRootLabels,
|
|
77
|
+
}),
|
|
78
|
+
"",
|
|
79
|
+
"## Repository Shape",
|
|
80
|
+
"",
|
|
81
|
+
renderRepositoryShape({
|
|
82
|
+
tree,
|
|
83
|
+
selected: presentationSelected,
|
|
84
|
+
showRootLabels,
|
|
85
|
+
}),
|
|
86
|
+
"",
|
|
87
|
+
"## Search Evidence",
|
|
88
|
+
"",
|
|
89
|
+
renderSearchEvidence({
|
|
90
|
+
search,
|
|
91
|
+
selected: presentationSelected,
|
|
92
|
+
showRootLabels,
|
|
93
|
+
}),
|
|
94
|
+
"",
|
|
95
|
+
"## File Context",
|
|
96
|
+
"",
|
|
97
|
+
renderFileContext(presentationSelected, search, showRootLabels),
|
|
98
|
+
"",
|
|
99
|
+
"## Open Questions",
|
|
100
|
+
"",
|
|
101
|
+
renderOpenQuestions(openQuestions),
|
|
102
|
+
"",
|
|
103
|
+
"## Omissions And Budget Notes",
|
|
104
|
+
"",
|
|
105
|
+
renderOmissionsAndBudgetNotes({
|
|
106
|
+
omissions: presentationOmissions,
|
|
107
|
+
warnings,
|
|
108
|
+
metrics: input.metrics,
|
|
109
|
+
}),
|
|
110
|
+
"",
|
|
111
|
+
].join("\n");
|
|
112
|
+
}
|
|
113
|
+
export function buildContextBuilderStatus(input) {
|
|
114
|
+
return reduceContextBuilderStatus(buildContextBuilderStatusEvidence(input));
|
|
115
|
+
}
|
|
116
|
+
export function buildContextBuilderStatusEvidence(input, boundedDiagnostics = {}) {
|
|
117
|
+
const omissions = boundedDiagnostics.omissions ?? input.omissions ?? [];
|
|
118
|
+
const warnings = boundedDiagnostics.warnings ?? input.warnings ?? [];
|
|
119
|
+
return {
|
|
120
|
+
...(input.status_evidence ?? {}),
|
|
121
|
+
omissions: [
|
|
122
|
+
...omissions.map(omissionEventFromDiagnostic),
|
|
123
|
+
...(input.status_evidence?.omissions ?? []),
|
|
124
|
+
],
|
|
125
|
+
warnings: [
|
|
126
|
+
...warnings.map(warningEventFromDiagnostic),
|
|
127
|
+
...(input.status_evidence?.warnings ?? []),
|
|
128
|
+
],
|
|
129
|
+
caps: [
|
|
130
|
+
...capEventsFromContextBuilderMetrics(input.metrics),
|
|
131
|
+
...(input.status_evidence?.caps ?? []),
|
|
132
|
+
],
|
|
133
|
+
storage: [
|
|
134
|
+
...(input.metrics.truncated
|
|
135
|
+
? [{ reason: "artifact_truncated" }]
|
|
136
|
+
: []),
|
|
137
|
+
...(input.status_evidence?.storage ?? []),
|
|
138
|
+
],
|
|
139
|
+
delegate: [
|
|
140
|
+
...(input.delegate?.fallback_used === true
|
|
141
|
+
? [{ reason: "delegate_fallback", fallback: true }]
|
|
142
|
+
: []),
|
|
143
|
+
...(input.status_evidence?.delegate ?? []),
|
|
144
|
+
],
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
export function renderContextBuilderArtifactContents(input) {
|
|
148
|
+
assertNoHighConfidenceSecretsInTask(input.task);
|
|
149
|
+
const tree = input.tree.map(sanitizeTreeFile);
|
|
150
|
+
const search = input.search.map(sanitizeSearchRecord);
|
|
151
|
+
const slices = input.slices.map(sanitizeSliceRecord);
|
|
152
|
+
const selectedContext = input.selected_context.map(sanitizeSelectedFile);
|
|
153
|
+
const summary = buildContextBuilderSummary(input);
|
|
154
|
+
return {
|
|
155
|
+
bundle_md: renderContextBuilderBundle(input),
|
|
156
|
+
tree_json: `${JSON.stringify({
|
|
157
|
+
schema_version: 1,
|
|
158
|
+
roots: input.roots.map(sanitizeRoot),
|
|
159
|
+
files: tree,
|
|
160
|
+
})}\n`,
|
|
161
|
+
search_jsonl: renderJsonl(search),
|
|
162
|
+
slices_jsonl: renderJsonl(slices),
|
|
163
|
+
codemap_jsonl: renderCodemapJsonl(selectedContext.map((file) => remapCodemapToSourceCoordinates(buildFileCodemap(file.path, file.content), file.ranges))),
|
|
164
|
+
summary_json: `${JSON.stringify({
|
|
165
|
+
...summary,
|
|
166
|
+
selected_context: selectedContext.map(stripSelectedContent),
|
|
167
|
+
})}\n`,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
export function buildContextBuilderManifestDraft(input) {
|
|
171
|
+
const internalCaps = contextBuilderInternalCapsForManifest(input.internal_caps);
|
|
172
|
+
const omissions = boundContextBuilderOmissions([...(input.omissions ?? [])], internalCaps);
|
|
173
|
+
const warnings = boundContextBuilderWarnings([...(input.warnings ?? [])], internalCaps);
|
|
174
|
+
const status = reduceContextBuilderStatus(buildContextBuilderStatusEvidence(input));
|
|
175
|
+
return {
|
|
176
|
+
status,
|
|
177
|
+
created_at: input.created_at,
|
|
178
|
+
task: validateTaskForRendering(input.task),
|
|
179
|
+
roots: input.roots.map(sanitizeRoot),
|
|
180
|
+
inputs: sanitizeJsonValue(input.inputs),
|
|
181
|
+
budget: { ...input.budget },
|
|
182
|
+
internal_caps: internalCaps,
|
|
183
|
+
token_counting: contextBuilderTokenCountingMethod(),
|
|
184
|
+
selected_context: input.selected_context
|
|
185
|
+
.map(sanitizeSelectedFile)
|
|
186
|
+
.map(stripSelectedContent),
|
|
187
|
+
omissions,
|
|
188
|
+
warnings,
|
|
189
|
+
metrics: { ...input.metrics },
|
|
190
|
+
...(input.delegate == null
|
|
191
|
+
? {}
|
|
192
|
+
: { delegate: cloneContextBuilderDelegateReceiptMetadata(input.delegate) }),
|
|
193
|
+
...(input.debug === true
|
|
194
|
+
? {
|
|
195
|
+
debug_diagnostics: boundDebugDiagnostics(input.debug_diagnostics, internalCaps),
|
|
196
|
+
}
|
|
197
|
+
: {}),
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
export async function writeContextBuilderFinalArtifacts(storage, input, options = {}) {
|
|
201
|
+
const { contents, metrics } = renderContentsWithStableByteMetric(input);
|
|
202
|
+
const writeInput = { ...input, metrics };
|
|
203
|
+
await writeContextBuilderArtifactsAndCommit(storage, contents, buildContextBuilderManifestDraft(writeInput), {
|
|
204
|
+
...(options.signal == null ? {} : { signal: options.signal }),
|
|
205
|
+
...(options.ownerNonce == null ? {} : { ownerNonce: options.ownerNonce }),
|
|
206
|
+
...(options.ops == null ? {} : { ops: options.ops }),
|
|
207
|
+
});
|
|
208
|
+
const internalCaps = contextBuilderInternalCapsForManifest(input.internal_caps);
|
|
209
|
+
const warnings = boundContextBuilderWarnings(input.warnings == null ? [] : [...input.warnings], internalCaps);
|
|
210
|
+
const omissions = boundContextBuilderOmissions(input.omissions == null ? [] : [...input.omissions], internalCaps);
|
|
211
|
+
return createContextBuilderReceipt({
|
|
212
|
+
run_id: storage.runId,
|
|
213
|
+
status: reduceContextBuilderStatus(buildContextBuilderStatusEvidence({ ...input, metrics })),
|
|
214
|
+
artifact_dir: storage.artifactDir,
|
|
215
|
+
artifacts: storage.artifacts,
|
|
216
|
+
metrics,
|
|
217
|
+
warnings,
|
|
218
|
+
omissions,
|
|
219
|
+
...(input.delegate == null
|
|
220
|
+
? {}
|
|
221
|
+
: { delegate: cloneContextBuilderDelegateReceiptMetadata(input.delegate) }),
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
function renderContentsWithStableByteMetric(input) {
|
|
225
|
+
let metrics = { ...input.metrics, bytes_written: 0 };
|
|
226
|
+
let contents = renderContextBuilderArtifactContents({ ...input, metrics });
|
|
227
|
+
for (let attempt = 0; attempt < 4; attempt += 1) {
|
|
228
|
+
const bytesWritten = Object.values(contents).reduce((total, content) => total + Buffer.byteLength(content, "utf8"), 0);
|
|
229
|
+
const nextMetrics = { ...metrics, bytes_written: bytesWritten };
|
|
230
|
+
const nextContents = renderContextBuilderArtifactContents({
|
|
231
|
+
...input,
|
|
232
|
+
metrics: nextMetrics,
|
|
233
|
+
});
|
|
234
|
+
if (bytesWritten === metrics.bytes_written &&
|
|
235
|
+
JSON.stringify(nextContents) === JSON.stringify(contents)) {
|
|
236
|
+
return { contents, metrics };
|
|
237
|
+
}
|
|
238
|
+
metrics = nextMetrics;
|
|
239
|
+
contents = nextContents;
|
|
240
|
+
}
|
|
241
|
+
return { contents, metrics };
|
|
242
|
+
}
|
|
243
|
+
function buildContextBuilderSummary(input) {
|
|
244
|
+
const selected = input.selected_context.map(sanitizeSelectedFile);
|
|
245
|
+
const omissions = reconcileSelectedOmissions(filterTaskPresentationOmissions(boundContextBuilderOmissions([...(input.omissions ?? [])], contextBuilderInternalCapsForManifest(input.internal_caps)), input.task), selected);
|
|
246
|
+
const openQuestions = buildPresentationOpenQuestions(boundOpenQuestions(input.open_questions ?? []), selected, omissions);
|
|
247
|
+
return {
|
|
248
|
+
schema_version: 1,
|
|
249
|
+
run_id: input.run_id,
|
|
250
|
+
created_at: input.created_at,
|
|
251
|
+
status: reduceContextBuilderStatus(buildContextBuilderStatusEvidence(input)),
|
|
252
|
+
token_counting: contextBuilderTokenCountingMethod(),
|
|
253
|
+
metrics: { ...input.metrics },
|
|
254
|
+
selected_file_count: input.selected_context.length,
|
|
255
|
+
warning_count: input.warnings?.length ?? 0,
|
|
256
|
+
omission_count: omissions.length,
|
|
257
|
+
open_questions: openQuestions,
|
|
258
|
+
...(input.delegate == null
|
|
259
|
+
? {}
|
|
260
|
+
: { delegate: cloneContextBuilderDelegateReceiptMetadata(input.delegate) }),
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
function sanitizeRoot(root) {
|
|
264
|
+
return {
|
|
265
|
+
root_index: root.root_index,
|
|
266
|
+
...(root.input_path == null ? {} : { input_path: safePath(root.input_path) }),
|
|
267
|
+
path: safePath(root.path),
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
function sanitizeTreeFile(file) {
|
|
271
|
+
return {
|
|
272
|
+
root_index: file.root_index,
|
|
273
|
+
path: safePath(file.path),
|
|
274
|
+
size_bytes: file.size_bytes,
|
|
275
|
+
eligible: file.eligible,
|
|
276
|
+
content_eligible: file.content_eligible,
|
|
277
|
+
...(file.omission_reason == null
|
|
278
|
+
? {}
|
|
279
|
+
: { omission_reason: sanitizeDiagnosticText(file.omission_reason) }),
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
function sanitizeSearchRecord(record) {
|
|
283
|
+
return {
|
|
284
|
+
root_index: record.root_index,
|
|
285
|
+
path: safePath(record.path),
|
|
286
|
+
line: record.line,
|
|
287
|
+
column: record.column,
|
|
288
|
+
search_term: sanitizeDiagnosticText(record.search_term),
|
|
289
|
+
search_term_index: record.search_term_index,
|
|
290
|
+
preview: safeContent(record.preview),
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
function sanitizeSliceRecord(record) {
|
|
294
|
+
return {
|
|
295
|
+
root_index: record.root_index,
|
|
296
|
+
path: safePath(record.path),
|
|
297
|
+
start_line: record.start_line,
|
|
298
|
+
end_line: record.end_line,
|
|
299
|
+
source: record.source,
|
|
300
|
+
content: safeContent(record.content),
|
|
301
|
+
...(record.search_term_index == null
|
|
302
|
+
? {}
|
|
303
|
+
: { search_term_index: record.search_term_index }),
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
function sanitizeSelectedFile(file) {
|
|
307
|
+
return {
|
|
308
|
+
root_index: file.root_index,
|
|
309
|
+
path: safePath(file.path),
|
|
310
|
+
ranges: file.ranges.map(sanitizeRange),
|
|
311
|
+
content: safeContent(file.content),
|
|
312
|
+
bytes: file.bytes,
|
|
313
|
+
estimated_tokens: file.estimated_tokens > 0
|
|
314
|
+
? file.estimated_tokens
|
|
315
|
+
: estimateContextBuilderTokens(file.content),
|
|
316
|
+
source: file.source,
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
function sanitizeRange(range) {
|
|
320
|
+
return {
|
|
321
|
+
start_line: range.start_line,
|
|
322
|
+
end_line: range.end_line,
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
function stripSelectedContent(file) {
|
|
326
|
+
return {
|
|
327
|
+
root_index: file.root_index,
|
|
328
|
+
path: file.path,
|
|
329
|
+
ranges: file.ranges.map(sanitizeRange),
|
|
330
|
+
bytes: file.bytes,
|
|
331
|
+
estimated_tokens: file.estimated_tokens,
|
|
332
|
+
source: file.source,
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
function renderSelectedFilesList(input) {
|
|
336
|
+
const { task, selected, search, showRootLabels } = input;
|
|
337
|
+
if (selected.length === 0) {
|
|
338
|
+
return "- None selected.";
|
|
339
|
+
}
|
|
340
|
+
const visible = selected.slice(0, 18);
|
|
341
|
+
const hiddenCount = selected.length - visible.length;
|
|
342
|
+
const rendered = visible
|
|
343
|
+
.map((file) => {
|
|
344
|
+
return [
|
|
345
|
+
`- ${renderRootPrefix(file.root_index, showRootLabels)}${file.path} (${file.source})`,
|
|
346
|
+
` purpose: ${selectedFilePurpose(task, file, search)}`,
|
|
347
|
+
].join("\n");
|
|
348
|
+
})
|
|
349
|
+
.join("\n");
|
|
350
|
+
return hiddenCount > 0
|
|
351
|
+
? `${rendered}\n- ${hiddenCount} more selected files are listed in manifest.json and slices.jsonl.`
|
|
352
|
+
: rendered;
|
|
353
|
+
}
|
|
354
|
+
function selectedFilePurpose(task, file, search) {
|
|
355
|
+
const action = taskActionCue(task);
|
|
356
|
+
const source = file.source === "focus" ? "requested focus evidence" : "matched task anchors";
|
|
357
|
+
const role = taskSpecificFileRole(task, file.path);
|
|
358
|
+
const clauses = [
|
|
359
|
+
action == null ? source : `${source} for ${action}`,
|
|
360
|
+
...(role == null ? [] : [role]),
|
|
361
|
+
pathPurpose(file.path),
|
|
362
|
+
rangePurpose(file.ranges),
|
|
363
|
+
];
|
|
364
|
+
const symbols = selectedFileSymbols(file);
|
|
365
|
+
if (symbols.length > 0) {
|
|
366
|
+
clauses.push(`symbols: ${symbols.join(", ")}`);
|
|
367
|
+
}
|
|
368
|
+
else {
|
|
369
|
+
const terms = selectedFileSearchTerms(file, search);
|
|
370
|
+
if (terms.length > 0) {
|
|
371
|
+
clauses.push(`search-backed slice`);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
return `${clauses.join("; ")}.`;
|
|
375
|
+
}
|
|
376
|
+
function selectedFileRangeNotes(task, file, search) {
|
|
377
|
+
const sourceLines = splitSelectedFileContentByRange(file);
|
|
378
|
+
const searchTerms = selectedFileSearchTerms(file, search);
|
|
379
|
+
return file.ranges.slice(0, 4).map((range, index) => {
|
|
380
|
+
const symbols = symbolsFromLines(sourceLines[index] ?? []);
|
|
381
|
+
const rangeHits = search.filter((record) => record.root_index === file.root_index &&
|
|
382
|
+
record.path === file.path &&
|
|
383
|
+
record.line >= range.start_line &&
|
|
384
|
+
record.line <= range.end_line);
|
|
385
|
+
const label = range.start_line === range.end_line
|
|
386
|
+
? `${range.start_line}`
|
|
387
|
+
: `${range.start_line}-${range.end_line}`;
|
|
388
|
+
return `${label}: ${rangeEvidenceSummary({
|
|
389
|
+
task,
|
|
390
|
+
path: file.path,
|
|
391
|
+
source: file.source,
|
|
392
|
+
symbols,
|
|
393
|
+
searchTerms,
|
|
394
|
+
rangeHits,
|
|
395
|
+
})}`;
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
function rangeEvidenceSummary(input) {
|
|
399
|
+
const semantic = taskSpecificRangeRole(input.task, input.path);
|
|
400
|
+
const hitSummary = rangeHitSummary(input.rangeHits ?? []);
|
|
401
|
+
if (semantic != null) {
|
|
402
|
+
return [
|
|
403
|
+
semantic,
|
|
404
|
+
...(hitSummary == null ? [] : [hitSummary]),
|
|
405
|
+
...(input.symbols.length === 0
|
|
406
|
+
? []
|
|
407
|
+
: [`key symbols ${input.symbols.slice(0, 4).join(", ")}`]),
|
|
408
|
+
].join("; ");
|
|
409
|
+
}
|
|
410
|
+
if (hitSummary != null) {
|
|
411
|
+
return hitSummary;
|
|
412
|
+
}
|
|
413
|
+
if (input.searchTerms.length > 0) {
|
|
414
|
+
return `${pathPurpose(input.path)} selected by matching task evidence`;
|
|
415
|
+
}
|
|
416
|
+
if (input.symbols.length > 0) {
|
|
417
|
+
return `${input.source} evidence around ${input.symbols.slice(0, 4).join(", ")}`;
|
|
418
|
+
}
|
|
419
|
+
return "bounded supporting evidence for the task";
|
|
420
|
+
}
|
|
421
|
+
function rangeHitSummary(hits) {
|
|
422
|
+
if (hits.length === 0) {
|
|
423
|
+
return undefined;
|
|
424
|
+
}
|
|
425
|
+
const byTerm = new Map();
|
|
426
|
+
for (const hit of hits) {
|
|
427
|
+
const term = hit.search_term.trim();
|
|
428
|
+
if (term.length === 0) {
|
|
429
|
+
continue;
|
|
430
|
+
}
|
|
431
|
+
const lines = byTerm.get(term) ?? [];
|
|
432
|
+
if (!lines.includes(hit.line)) {
|
|
433
|
+
lines.push(hit.line);
|
|
434
|
+
}
|
|
435
|
+
byTerm.set(term, lines);
|
|
436
|
+
if (byTerm.size >= 3) {
|
|
437
|
+
break;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
const rendered = [...byTerm.entries()]
|
|
441
|
+
.slice(0, 3)
|
|
442
|
+
.map(([term, lines]) => `"${term}" at ${lines.slice(0, 3).join(",")}`);
|
|
443
|
+
return rendered.length === 0 ? undefined : `matches ${rendered.join("; ")}`;
|
|
444
|
+
}
|
|
445
|
+
function selectedFileSymbols(file) {
|
|
446
|
+
return symbolsFromLines(splitTextLinesForBundle(file.content)).slice(0, 6);
|
|
447
|
+
}
|
|
448
|
+
function splitSelectedFileContentByRange(file) {
|
|
449
|
+
const lines = splitTextLinesForBundle(file.content);
|
|
450
|
+
if (file.ranges.length <= 1) {
|
|
451
|
+
return [lines];
|
|
452
|
+
}
|
|
453
|
+
const totalRangeLines = file.ranges.reduce((total, range) => total + Math.max(0, range.end_line - range.start_line + 1), 0);
|
|
454
|
+
if (totalRangeLines !== lines.length) {
|
|
455
|
+
return file.ranges.map(() => []);
|
|
456
|
+
}
|
|
457
|
+
const output = [];
|
|
458
|
+
let offset = 0;
|
|
459
|
+
for (const range of file.ranges) {
|
|
460
|
+
const count = Math.max(0, range.end_line - range.start_line + 1);
|
|
461
|
+
output.push(lines.slice(offset, offset + count));
|
|
462
|
+
offset += count;
|
|
463
|
+
}
|
|
464
|
+
return output;
|
|
465
|
+
}
|
|
466
|
+
function splitTextLinesForBundle(text) {
|
|
467
|
+
if (text.length === 0) {
|
|
468
|
+
return [];
|
|
469
|
+
}
|
|
470
|
+
const withoutFinalNewline = text.endsWith("\n")
|
|
471
|
+
? text.slice(0, text.endsWith("\r\n") ? -2 : -1)
|
|
472
|
+
: text;
|
|
473
|
+
return withoutFinalNewline.length === 0
|
|
474
|
+
? [""]
|
|
475
|
+
: withoutFinalNewline.split(/\r\n|\n/);
|
|
476
|
+
}
|
|
477
|
+
function symbolsFromLines(lines) {
|
|
478
|
+
const symbols = [];
|
|
479
|
+
const seen = new Set();
|
|
480
|
+
for (const line of lines) {
|
|
481
|
+
for (const symbol of symbolsFromLine(line)) {
|
|
482
|
+
if (seen.has(symbol)) {
|
|
483
|
+
continue;
|
|
484
|
+
}
|
|
485
|
+
seen.add(symbol);
|
|
486
|
+
symbols.push(symbol);
|
|
487
|
+
if (symbols.length >= 8) {
|
|
488
|
+
return symbols;
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
return symbols;
|
|
493
|
+
}
|
|
494
|
+
function symbolsFromLine(line) {
|
|
495
|
+
const patterns = [
|
|
496
|
+
/\b(?:async\s+)?(?:def|function|class)\s+([A-Za-z_][A-Za-z0-9_]*)/g,
|
|
497
|
+
/\b(?:const|let|var)\s+([A-Za-z_][A-Za-z0-9_]*)\s*=/g,
|
|
498
|
+
/\bexport\s+(?:async\s+)?(?:function|class|const)\s+([A-Za-z_][A-Za-z0-9_]*)/g,
|
|
499
|
+
/\bit\(\s*['"]([^'"]{8,90})['"]/g,
|
|
500
|
+
/\bdescribe\(\s*['"]([^'"]{8,90})['"]/g,
|
|
501
|
+
];
|
|
502
|
+
const symbols = [];
|
|
503
|
+
for (const pattern of patterns) {
|
|
504
|
+
for (const match of line.matchAll(pattern)) {
|
|
505
|
+
const value = match[1]?.trim();
|
|
506
|
+
if (value != null && value.length > 0) {
|
|
507
|
+
symbols.push(value);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
return symbols;
|
|
512
|
+
}
|
|
513
|
+
function selectedFileSearchTerms(file, search) {
|
|
514
|
+
const terms = [];
|
|
515
|
+
const seen = new Set();
|
|
516
|
+
for (const record of search) {
|
|
517
|
+
if (record.root_index !== file.root_index || record.path !== file.path) {
|
|
518
|
+
continue;
|
|
519
|
+
}
|
|
520
|
+
const term = record.search_term.trim();
|
|
521
|
+
if (term.length === 0 || seen.has(term)) {
|
|
522
|
+
continue;
|
|
523
|
+
}
|
|
524
|
+
seen.add(term);
|
|
525
|
+
terms.push(term);
|
|
526
|
+
if (terms.length >= 4) {
|
|
527
|
+
break;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
return terms;
|
|
531
|
+
}
|
|
532
|
+
function taskSpecificFileRole(task, path) {
|
|
533
|
+
const normalizedTask = task.toLowerCase();
|
|
534
|
+
const branch = classifyTaskBranch(normalizedTask);
|
|
535
|
+
const normalized = path.toLowerCase();
|
|
536
|
+
if (branch === "runtime") {
|
|
537
|
+
if (normalized.includes("model_provider_auth") || normalized.includes("ai_models.py")) {
|
|
538
|
+
return "gates model exposure and Codex availability from provider metadata";
|
|
539
|
+
}
|
|
540
|
+
if (normalized.includes("/runtime/resolver.py") || normalized.includes("ai_model.py")) {
|
|
541
|
+
return "routes model/provider metadata into Claude vs Codex runtime selection";
|
|
542
|
+
}
|
|
543
|
+
if (normalized.includes("thinkingmodeselector") || normalized.includes("permissionmodeselector")) {
|
|
544
|
+
return "defines visible effort/permission controls whose values feed runtime settings";
|
|
545
|
+
}
|
|
546
|
+
if (normalized.includes("message-input") || normalized.includes("usechatruntime")) {
|
|
547
|
+
return "captures frontend runtime-setting controls";
|
|
548
|
+
}
|
|
549
|
+
if (normalized.includes("messagequeue") || normalized.includes("queue")) {
|
|
550
|
+
return "checks queued-send persistence and retry behavior";
|
|
551
|
+
}
|
|
552
|
+
if (normalized.endsWith("/api/endpoints/chat.py")) {
|
|
553
|
+
return "validates backend chat request ingestion";
|
|
554
|
+
}
|
|
555
|
+
if (normalized.endsWith("/services/chat.py")) {
|
|
556
|
+
return "connects persisted settings to completion dispatch";
|
|
557
|
+
}
|
|
558
|
+
if (normalized.includes("test_claude_agent")) {
|
|
559
|
+
return "tests Claude permission and effort translation semantics";
|
|
560
|
+
}
|
|
561
|
+
if (normalized.includes("/runtime/") || normalized.includes("claude_agent")) {
|
|
562
|
+
return "anchors provider-specific permission and effort semantics";
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
if (branch === "review") {
|
|
566
|
+
if (normalized.includes("codex_approval") || normalized.includes("permissions")) {
|
|
567
|
+
return "anchors approval and permission contract risk";
|
|
568
|
+
}
|
|
569
|
+
if (normalized.includes("/runtime/") || normalized.includes("runtime_session")) {
|
|
570
|
+
return "anchors Codex runtime/session behavior";
|
|
571
|
+
}
|
|
572
|
+
if (normalized.includes("fileupload") || normalized.includes("usemessageactions")) {
|
|
573
|
+
return "anchors frontend provider and attachment contract";
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
if (branch === "debug") {
|
|
577
|
+
if (normalized.includes("rightpaneldevtools") || normalized.includes("devtoolsnavstore")) {
|
|
578
|
+
return "anchors trace row identity and navigation state";
|
|
579
|
+
}
|
|
580
|
+
if (normalized.includes("trace_archive") || normalized.includes("devtools.py")) {
|
|
581
|
+
return "anchors backend trace entry shape";
|
|
582
|
+
}
|
|
583
|
+
if (normalized.includes("chat-window") || normalized.includes("message") || normalized.includes("tool")) {
|
|
584
|
+
return "anchors message or tool highlight target resolution";
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
return undefined;
|
|
588
|
+
}
|
|
589
|
+
function taskSpecificRangeRole(task, path) {
|
|
590
|
+
const normalizedTask = task.toLowerCase();
|
|
591
|
+
const branch = classifyTaskBranch(normalizedTask);
|
|
592
|
+
const normalized = path.toLowerCase();
|
|
593
|
+
if (branch === "runtime") {
|
|
594
|
+
if (normalized.includes("model_provider_auth") || normalized.includes("ai_models.py")) {
|
|
595
|
+
return "checks model exposure and provider capability gating for Codex runtime";
|
|
596
|
+
}
|
|
597
|
+
if (normalized.includes("/runtime/resolver.py") || normalized.includes("ai_model.py")) {
|
|
598
|
+
return "resolves selected model metadata into the provider runtime boundary";
|
|
599
|
+
}
|
|
600
|
+
if (normalized.includes("modelprovider.ts")) {
|
|
601
|
+
return "maps frontend model/provider capability checks driving runtime controls";
|
|
602
|
+
}
|
|
603
|
+
if (normalized.includes("thinkingmodeselector") || normalized.includes("permissionmodeselector")) {
|
|
604
|
+
return "owns the visible effort/permission mode choices and stale-value reset behavior";
|
|
605
|
+
}
|
|
606
|
+
if (normalized.includes("input.tsx")) {
|
|
607
|
+
return "owns composer send wiring and runtime-setting handoff from UI state";
|
|
608
|
+
}
|
|
609
|
+
if (normalized.includes("chatpage")) {
|
|
610
|
+
return "connects chat creation, initial prompt dispatch, and page-level runtime state";
|
|
611
|
+
}
|
|
612
|
+
if (normalized.includes("composerruntimecontrols")) {
|
|
613
|
+
return "defines visible permission and effort controls";
|
|
614
|
+
}
|
|
615
|
+
if (normalized.includes("inputsettingspopover")) {
|
|
616
|
+
return "maps stored runtime settings into composer controls";
|
|
617
|
+
}
|
|
618
|
+
if (normalized.includes("usechatruntime")) {
|
|
619
|
+
return "reads and writes chat-scoped runtime snapshots";
|
|
620
|
+
}
|
|
621
|
+
if (normalized.includes("usemessageactions")) {
|
|
622
|
+
return "serializes runtime settings on normal send and queued append";
|
|
623
|
+
}
|
|
624
|
+
if (normalized.includes("chatservice")) {
|
|
625
|
+
return "defines create-chat runtime fields and send/queue compatibility fields";
|
|
626
|
+
}
|
|
627
|
+
if (normalized.includes("messagequeue") || normalized.includes("queueservice")) {
|
|
628
|
+
return "preserves permission and effort across local queue replay";
|
|
629
|
+
}
|
|
630
|
+
if (normalized.includes("streamservice")) {
|
|
631
|
+
return "streams backend responses after send/queue runtime settings are accepted";
|
|
632
|
+
}
|
|
633
|
+
if (normalized.endsWith("/api/endpoints/chat.py")) {
|
|
634
|
+
return "parses create-chat runtime fields and send/queue compatibility fields";
|
|
635
|
+
}
|
|
636
|
+
if (normalized.endsWith("/models/schemas/chat.py") || normalized.endsWith("chat.types.ts")) {
|
|
637
|
+
return "defines the request/response contract fields that must not drift";
|
|
638
|
+
}
|
|
639
|
+
if (normalized.endsWith("queue.types.ts")) {
|
|
640
|
+
return "defines queued-send payload fields carried through replay";
|
|
641
|
+
}
|
|
642
|
+
if (normalized.endsWith("/services/chat.py")) {
|
|
643
|
+
return "persists runtime snapshots and redispatches pending initial prompts";
|
|
644
|
+
}
|
|
645
|
+
if (normalized.includes("test_queue")) {
|
|
646
|
+
return "guards queue replay of runtime permission and effort payloads";
|
|
647
|
+
}
|
|
648
|
+
if (normalized.includes("test_effort_level") || normalized.includes("test_permission_mode")) {
|
|
649
|
+
return "guards backend validation of runtime setting values";
|
|
650
|
+
}
|
|
651
|
+
if (normalized.includes("test_codex_runtime") || normalized.includes("test_codex_effort")) {
|
|
652
|
+
return "guards Codex runtime translation and migration behavior";
|
|
653
|
+
}
|
|
654
|
+
if (normalized.includes("usepermissionrequest")) {
|
|
655
|
+
return "shows frontend permission request consumption during runtime approval flows";
|
|
656
|
+
}
|
|
657
|
+
if (normalized.includes("usesettingsqueries")) {
|
|
658
|
+
return "shows how persisted settings are fetched back into runtime controls";
|
|
659
|
+
}
|
|
660
|
+
if (normalized.includes("test_claude_agent")) {
|
|
661
|
+
return "verifies Claude permission mapping, bypass sentinel handling, and effort levels";
|
|
662
|
+
}
|
|
663
|
+
if (normalized.includes("/runtime/") || normalized.includes("claude_agent")) {
|
|
664
|
+
return "translates normalized runtime settings into provider-specific options";
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
if (branch === "review") {
|
|
668
|
+
if (normalized.includes("codex_approval")) {
|
|
669
|
+
return "maps SDK approval requests to permission API payloads and timeouts";
|
|
670
|
+
}
|
|
671
|
+
if (normalized.includes("permission_manager") || normalized.includes("permissions")) {
|
|
672
|
+
return "stores, fetches, and resolves approval requests across Redis and HTTP";
|
|
673
|
+
}
|
|
674
|
+
if (normalized.includes("runtime_session")) {
|
|
675
|
+
return "decides whether Codex runtime sessions resume or start fresh";
|
|
676
|
+
}
|
|
677
|
+
if (normalized.includes("codex_stream_processor")) {
|
|
678
|
+
return "accounts for Codex stream lifecycle and usage snapshots";
|
|
679
|
+
}
|
|
680
|
+
if (normalized.includes("/runtime/codex.py")) {
|
|
681
|
+
return "owns Codex runtime process lifecycle, approval callbacks, and stream reads";
|
|
682
|
+
}
|
|
683
|
+
if (normalized.includes("/runtime/resolver.py")) {
|
|
684
|
+
return "decides Codex-vs-Claude runtime routing from model/provider metadata";
|
|
685
|
+
}
|
|
686
|
+
if (normalized.includes("/runtime/base.py") || normalized.includes("/runtime/claude.py")) {
|
|
687
|
+
return "anchors shared runtime contracts used to compare Codex behavior";
|
|
688
|
+
}
|
|
689
|
+
if (normalized.includes("model_provider_auth") || normalized.includes("ai_models.py")) {
|
|
690
|
+
return "checks provider metadata and Codex availability gates";
|
|
691
|
+
}
|
|
692
|
+
if (normalized.includes("test_ai_models_codex_gate") || normalized.includes("test_model_provider_auth")) {
|
|
693
|
+
return "guards provider gating and metadata fail-closed behavior";
|
|
694
|
+
}
|
|
695
|
+
if (normalized.includes("modelprovider.ts")) {
|
|
696
|
+
return "maps frontend model/provider capability checks";
|
|
697
|
+
}
|
|
698
|
+
if (normalized.includes("input.tsx")) {
|
|
699
|
+
return "owns primary send-site attachment/runtime handoff behavior";
|
|
700
|
+
}
|
|
701
|
+
if (normalized.includes("fileupload") || normalized.includes("usemessageactions")) {
|
|
702
|
+
return "guards frontend attachment behavior for Codex provider constraints";
|
|
703
|
+
}
|
|
704
|
+
if (normalized.includes("chatservice") || normalized.endsWith("/api/endpoints/chat.py") || normalized.endsWith("/services/chat.py")) {
|
|
705
|
+
return "checks chat ingress and persistence paths touched by runtime/provider decisions";
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
if (branch === "debug") {
|
|
709
|
+
if (normalized.includes("rightpaneldevtools")) {
|
|
710
|
+
return "tracks filtered rows, virtualizer keys, and clicked trace-entry identity";
|
|
711
|
+
}
|
|
712
|
+
if (normalized.includes("devtools-helpers")) {
|
|
713
|
+
return "classifies trace entries into message and tool navigation targets";
|
|
714
|
+
}
|
|
715
|
+
if (normalized.includes("devtoolsnavstore")) {
|
|
716
|
+
return "queues and delivers right-panel navigation requests";
|
|
717
|
+
}
|
|
718
|
+
if (normalized.includes("chat-window/chat")) {
|
|
719
|
+
return "resolves pending navigation against cached messages and scroll state";
|
|
720
|
+
}
|
|
721
|
+
if (normalized.includes("message-bubble/message.tsx")) {
|
|
722
|
+
return "wraps message highlight lifecycle after navigation resolves";
|
|
723
|
+
}
|
|
724
|
+
if (normalized.includes("messagecacheorder")) {
|
|
725
|
+
return "keeps cached messages in stable chronological order for lookup";
|
|
726
|
+
}
|
|
727
|
+
if (normalized.includes("messagerenderer")) {
|
|
728
|
+
return "turns message and tool identifiers into highlightable anchors";
|
|
729
|
+
}
|
|
730
|
+
if (normalized.includes("toolcard")) {
|
|
731
|
+
return "expands nested tool cards and exposes anchor ids";
|
|
732
|
+
}
|
|
733
|
+
if (normalized.includes("usedevtoolsqueries") || normalized.includes("querykeys")) {
|
|
734
|
+
return "defines trace query cache keys and freshness boundaries";
|
|
735
|
+
}
|
|
736
|
+
if (normalized.includes("right-panel-devtools.test")) {
|
|
737
|
+
return "guards filtered row click-through and trace navigation behavior";
|
|
738
|
+
}
|
|
739
|
+
if (normalized.includes("devtoolsnavstore.test")) {
|
|
740
|
+
return "guards navigation request delivery and highlight clearing behavior";
|
|
741
|
+
}
|
|
742
|
+
if (normalized.includes("devtools.py") || normalized.includes("session_trace")) {
|
|
743
|
+
return "defines backend trace/archive response shape and expiry fallback";
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
return undefined;
|
|
747
|
+
}
|
|
748
|
+
function pathPurpose(path) {
|
|
749
|
+
const normalized = path.toLowerCase();
|
|
750
|
+
const basename = normalized.split("/").at(-1) ?? normalized;
|
|
751
|
+
const language = languagePurpose(basename);
|
|
752
|
+
if (isTestPath(normalized, basename)) {
|
|
753
|
+
return `${language} test coverage`;
|
|
754
|
+
}
|
|
755
|
+
if (normalized.startsWith("docs/") || normalized.includes("/docs/")) {
|
|
756
|
+
return `${language} documentation`;
|
|
757
|
+
}
|
|
758
|
+
if (isClosurePath(normalized)) {
|
|
759
|
+
return `${language} contract or configuration`;
|
|
760
|
+
}
|
|
761
|
+
if (normalized.startsWith("frontend/") || normalized.includes("/frontend/")) {
|
|
762
|
+
return `${language} frontend implementation`;
|
|
763
|
+
}
|
|
764
|
+
if (normalized.startsWith("backend/") || normalized.includes("/backend/")) {
|
|
765
|
+
return `${language} backend implementation`;
|
|
766
|
+
}
|
|
767
|
+
if (normalized.startsWith("src/") || normalized.includes("/src/")) {
|
|
768
|
+
return `${language} source implementation`;
|
|
769
|
+
}
|
|
770
|
+
return `${language} selected context`;
|
|
771
|
+
}
|
|
772
|
+
function languagePurpose(basename) {
|
|
773
|
+
if (basename.endsWith(".tsx")) {
|
|
774
|
+
return "TSX";
|
|
775
|
+
}
|
|
776
|
+
if (basename.endsWith(".ts")) {
|
|
777
|
+
return "TypeScript";
|
|
778
|
+
}
|
|
779
|
+
if (basename.endsWith(".jsx")) {
|
|
780
|
+
return "JSX";
|
|
781
|
+
}
|
|
782
|
+
if (basename.endsWith(".js")) {
|
|
783
|
+
return "JavaScript";
|
|
784
|
+
}
|
|
785
|
+
if (basename.endsWith(".py")) {
|
|
786
|
+
return "Python";
|
|
787
|
+
}
|
|
788
|
+
if (basename.endsWith(".rs")) {
|
|
789
|
+
return "Rust";
|
|
790
|
+
}
|
|
791
|
+
if (basename.endsWith(".md") || basename.endsWith(".mdx")) {
|
|
792
|
+
return "Markdown";
|
|
793
|
+
}
|
|
794
|
+
if (basename.endsWith(".json")) {
|
|
795
|
+
return "JSON";
|
|
796
|
+
}
|
|
797
|
+
if (basename.endsWith(".yml") || basename.endsWith(".yaml")) {
|
|
798
|
+
return "YAML";
|
|
799
|
+
}
|
|
800
|
+
return "file";
|
|
801
|
+
}
|
|
802
|
+
function isTestPath(path, basename) {
|
|
803
|
+
return (path.startsWith("test/") ||
|
|
804
|
+
path.startsWith("tests/") ||
|
|
805
|
+
path.includes("/test/") ||
|
|
806
|
+
path.includes("/tests/") ||
|
|
807
|
+
path.includes("/__tests__/") ||
|
|
808
|
+
basename.includes(".test.") ||
|
|
809
|
+
basename.includes(".spec.") ||
|
|
810
|
+
basename.startsWith("test_"));
|
|
811
|
+
}
|
|
812
|
+
function rangePurpose(ranges) {
|
|
813
|
+
if (ranges.length === 0) {
|
|
814
|
+
return "full-file context";
|
|
815
|
+
}
|
|
816
|
+
const rendered = ranges
|
|
817
|
+
.slice(0, 3)
|
|
818
|
+
.map((range) => range.start_line === range.end_line
|
|
819
|
+
? `${range.start_line}`
|
|
820
|
+
: `${range.start_line}-${range.end_line}`);
|
|
821
|
+
const suffix = ranges.length > rendered.length ? ` +${ranges.length - rendered.length}` : "";
|
|
822
|
+
return `ranges ${rendered.join(", ")}${suffix}`;
|
|
823
|
+
}
|
|
824
|
+
function renderTaskGuidance(input) {
|
|
825
|
+
const normalizedTask = input.task.toLowerCase();
|
|
826
|
+
const branch = classifyTaskBranch(normalizedTask);
|
|
827
|
+
const selected = selectedPathSet(input.selected);
|
|
828
|
+
if (branch === "runtime") {
|
|
829
|
+
return [
|
|
830
|
+
`- Top targets: ${guidanceCoverage(selected, input.showRootLabels, [
|
|
831
|
+
"frontend/src/hooks/useChatRuntimeSettings.ts",
|
|
832
|
+
"frontend/src/hooks/useMessageActions.ts",
|
|
833
|
+
"frontend/src/store/messageQueueStore.ts",
|
|
834
|
+
"backend/app/api/endpoints/chat.py",
|
|
835
|
+
"backend/app/services/chat.py",
|
|
836
|
+
"backend/app/services/queue.py",
|
|
837
|
+
"backend/app/services/streaming/orchestrator.py",
|
|
838
|
+
"backend/app/services/runtime/codex.py",
|
|
839
|
+
"backend/app/services/runtime/claude.py",
|
|
840
|
+
])}.`,
|
|
841
|
+
"- Flow to verify: composer controls and chat-scoped settings produce permission/effort values; create-chat sends runtime_permission_mode/runtime_effort_level, normal send and queue paths send permission_mode/effort_level compatibility fields, and backend code normalizes/persists one runtime snapshot before dispatch.",
|
|
842
|
+
"- Provider semantics to pin down: Codex effort values collapse to its advertised levels, Claude permission modes can map to bypass-style options, legacy/internal sentinel values must normalize before persistence, and unsupported settings must fail loudly rather than vanish.",
|
|
843
|
+
"- Validation checklist: create-chat stores runtime_permission_mode/runtime_effort_level; queued local append and backend upsert preserve permission_mode/effort_level until API/service normalization; pending initial prompt redispatch reads the stored snapshot; provider adapter tests cover translation drift.",
|
|
844
|
+
].join("\n");
|
|
845
|
+
}
|
|
846
|
+
if (branch === "review") {
|
|
847
|
+
return [
|
|
848
|
+
`- Top review targets: ${guidanceCoverage(selected, input.showRootLabels, [
|
|
849
|
+
"backend/app/services/runtime/resolver.py",
|
|
850
|
+
"backend/app/services/runtime/codex.py",
|
|
851
|
+
"backend/app/services/runtime/codex_approval.py",
|
|
852
|
+
"backend/app/services/runtime/codex_stream_processor.py",
|
|
853
|
+
"backend/app/services/runtime_session_service.py",
|
|
854
|
+
"backend/app/api/endpoints/permissions.py",
|
|
855
|
+
"frontend/src/hooks/usePermissionRequest.ts",
|
|
856
|
+
"frontend/src/hooks/useMessageActions.ts",
|
|
857
|
+
])}.`,
|
|
858
|
+
"- Review scope: this packet contains selected current-source context, not a git diff; separate confirmed source facts, likely risks, and needs-verification items before labeling anything a finding.",
|
|
859
|
+
"- Likely risks to test first: fail-open runtime routing when Codex support metadata is absent; wrong-chat or stale RuntimeSessionService resume decisions; approval timeout/Redis TTL mismatches; denied or expired permission responses mutating the wrong pending request; frontend file-upload filtering drifting from provider capabilities.",
|
|
860
|
+
"- Evidence map: pair runtime/session files with codex_approval.py and permission endpoints, then check frontend hooks/tests for the user-visible approval and attachment behavior.",
|
|
861
|
+
"- Missing-test checklist: live SDK approval payload shape, timeout cleanup of reader tasks, cross-chat permission denial, expired permission UI behavior, and provider-specific attachment filtering.",
|
|
862
|
+
].join("\n");
|
|
863
|
+
}
|
|
864
|
+
if (branch === "debug") {
|
|
865
|
+
return [
|
|
866
|
+
`- Top debug targets: ${guidanceCoverage(selected, input.showRootLabels, [
|
|
867
|
+
"frontend/src/components/right-panel/RightPanelDevtools.tsx",
|
|
868
|
+
"frontend/src/components/right-panel/devtools-helpers.ts",
|
|
869
|
+
"frontend/src/store/devtoolsNavStore.ts",
|
|
870
|
+
"frontend/src/components/chat/chat-window/Chat.tsx",
|
|
871
|
+
"frontend/src/utils/messageCacheOrder.ts",
|
|
872
|
+
"frontend/src/components/chat/message-bubble/MessageRenderer.tsx",
|
|
873
|
+
"frontend/src/components/chat/tools/common/ToolCard.tsx",
|
|
874
|
+
"backend/app/api/endpoints/devtools.py",
|
|
875
|
+
])}.`,
|
|
876
|
+
"- Confirmed source fact to preserve: selected RightPanelDevtools source may already carry originalIndex from the filtered row into TraceEntry and handleEntryClick, so originalIndex/filter-index alone is not proven as the main root cause.",
|
|
877
|
+
"- Likely checks: deferred search/navigation staleness, virtualizer key reuse, layout-effect timing after filter/search changes, cached-message dedupe before target resolution, and MessageRenderer/ToolCard anchor expansion.",
|
|
878
|
+
"- Validation checklist: filtered views click the intended message/tool, virtualized rows keep stable keys after filter changes, layout effects remeasure at the right time, cached message flattening preserves the target, and regression tests cover both message and nested tool anchors.",
|
|
879
|
+
].join("\n");
|
|
880
|
+
}
|
|
881
|
+
return "- Use focus files first, then closure contracts and tests; material omissions or open questions should be resolved before editing.";
|
|
882
|
+
}
|
|
883
|
+
function guidanceCoverage(selected, showRootLabels, expectedPaths) {
|
|
884
|
+
const present = expectedPaths
|
|
885
|
+
.filter((path) => selected.has(path))
|
|
886
|
+
.slice(0, 10)
|
|
887
|
+
.map((path) => renderRootPrefix(0, showRootLabels) + path);
|
|
888
|
+
return present.length === 0
|
|
889
|
+
? "no expected top target selected; inspect selected files and omissions"
|
|
890
|
+
: present.join(", ");
|
|
891
|
+
}
|
|
892
|
+
function renderContextSynthesis(input) {
|
|
893
|
+
const lines = [];
|
|
894
|
+
const selectedPaths = new Set(input.selected.map((file) => `${file.root_index}\u0000${file.path}`));
|
|
895
|
+
const selectedSearch = input.search.filter((record) => selectedPaths.has(`${record.root_index}\u0000${record.path}`));
|
|
896
|
+
const selectedTerms = [
|
|
897
|
+
...new Set(selectedSearch.map((record) => record.search_term).filter(Boolean)),
|
|
898
|
+
].slice(0, 8);
|
|
899
|
+
const materialOmissions = input.omissions.filter(isMaterialOmission);
|
|
900
|
+
const taskShape = renderTaskShapeSummary({
|
|
901
|
+
task: input.task,
|
|
902
|
+
selected: input.selected,
|
|
903
|
+
selectedSearch,
|
|
904
|
+
materialOmissions,
|
|
905
|
+
showRootLabels: input.showRootLabels,
|
|
906
|
+
});
|
|
907
|
+
lines.push(`- Coverage: ${input.metrics.files_selected} selected files, ${input.omissions.length} recorded omissions, ${input.metrics.estimated_tokens} estimated tokens.`);
|
|
908
|
+
lines.push(...taskShape);
|
|
909
|
+
if (selectedTerms.length > 0) {
|
|
910
|
+
lines.push(`- Search evidence: selected-file hits cover ${selectedTerms.length} task anchors; exact hit lines are in search.jsonl.`);
|
|
911
|
+
}
|
|
912
|
+
const groups = renderSelectedPathGroups(input.selected);
|
|
913
|
+
if (groups.length > 0) {
|
|
914
|
+
lines.push(`- Selected areas: ${groups.join("; ")}.`);
|
|
915
|
+
}
|
|
916
|
+
const closure = closureFiles(input.selected, input.showRootLabels);
|
|
917
|
+
if (closure.length > 0) {
|
|
918
|
+
lines.push(`- Contract and closure files: ${closure.join(", ")}.`);
|
|
919
|
+
}
|
|
920
|
+
if (materialOmissions.length > 0) {
|
|
921
|
+
const rendered = materialOmissions
|
|
922
|
+
.slice(0, 5)
|
|
923
|
+
.map((omission) => {
|
|
924
|
+
return `${humanOmissionReason(omission.reason)}${renderOmissionPathSuffix(omission.paths, 3)}`;
|
|
925
|
+
})
|
|
926
|
+
.join("; ");
|
|
927
|
+
lines.push(`- Material follow-ups: ${rendered}.`);
|
|
928
|
+
}
|
|
929
|
+
else {
|
|
930
|
+
lines.push("- Material follow-ups: none recorded.");
|
|
931
|
+
}
|
|
932
|
+
return lines.join("\n");
|
|
933
|
+
}
|
|
934
|
+
function renderTaskShapeSummary(input) {
|
|
935
|
+
const normalizedTask = input.task.toLowerCase();
|
|
936
|
+
const selected = selectedPathSet(input.selected);
|
|
937
|
+
const branch = classifyTaskBranch(normalizedTask);
|
|
938
|
+
const taskBullets = branch === "runtime"
|
|
939
|
+
? runtimeSettingsSynthesisBullets(normalizedTask, selected)
|
|
940
|
+
: branch === "review"
|
|
941
|
+
? codexApprovalsSynthesisBullets(normalizedTask, selected)
|
|
942
|
+
: branch === "debug"
|
|
943
|
+
? devtoolsTraceSynthesisBullets(normalizedTask, selected)
|
|
944
|
+
: [
|
|
945
|
+
...runtimeSettingsSynthesisBullets(normalizedTask, selected),
|
|
946
|
+
...codexApprovalsSynthesisBullets(normalizedTask, selected),
|
|
947
|
+
...devtoolsTraceSynthesisBullets(normalizedTask, selected),
|
|
948
|
+
];
|
|
949
|
+
if (taskBullets.length > 0) {
|
|
950
|
+
return taskBullets;
|
|
951
|
+
}
|
|
952
|
+
const action = taskActionCue(input.task) ?? "not inferred";
|
|
953
|
+
const relationshipCues = relationshipCuesFromSelected(input.selected);
|
|
954
|
+
const riskCues = riskCuesFromContext({
|
|
955
|
+
selected: input.selected,
|
|
956
|
+
selectedSearch: input.selectedSearch,
|
|
957
|
+
materialOmissions: input.materialOmissions,
|
|
958
|
+
showRootLabels: input.showRootLabels,
|
|
959
|
+
});
|
|
960
|
+
return [
|
|
961
|
+
`- Task shape: action=${action}; relationships=${relationshipCues}; risks=${riskCues}.`,
|
|
962
|
+
];
|
|
963
|
+
}
|
|
964
|
+
function renderTopMustReadRanges(input) {
|
|
965
|
+
if (input.selected.length === 0) {
|
|
966
|
+
return "- No selected ranges available.";
|
|
967
|
+
}
|
|
968
|
+
const candidates = topMustReadRangeCandidates(input)
|
|
969
|
+
.sort((left, right) => right.score - left.score || left.order - right.order)
|
|
970
|
+
.slice(0, 8);
|
|
971
|
+
return candidates
|
|
972
|
+
.map((candidate, index) => {
|
|
973
|
+
const header = `${index + 1}. ${renderRootPrefix(candidate.file.root_index, input.showRootLabels)}${candidate.file.path}:${singleRangeLabel(candidate.range)} - ${candidate.reason}`;
|
|
974
|
+
const snippet = renderTopMustReadSnippet(candidate, index, input.task);
|
|
975
|
+
return snippet.length === 0 ? header : `${header}\n\n${snippet}`;
|
|
976
|
+
})
|
|
977
|
+
.join("\n");
|
|
978
|
+
}
|
|
979
|
+
function topMustReadRangeCandidates(input) {
|
|
980
|
+
const branch = classifyTaskBranch(input.task.toLowerCase());
|
|
981
|
+
const branchLabel = `${branch === "generic" ? "general" : branch} task`;
|
|
982
|
+
const candidates = [];
|
|
983
|
+
let order = 0;
|
|
984
|
+
for (const file of input.selected) {
|
|
985
|
+
const searchTerms = selectedFileSearchTerms(file, input.search);
|
|
986
|
+
const searchHitCount = input.search.filter((record) => record.root_index === file.root_index && record.path === file.path).length;
|
|
987
|
+
const sourceLines = splitSelectedFileContentByRange(file);
|
|
988
|
+
for (const [rangeIndex, range] of file.ranges.entries()) {
|
|
989
|
+
const symbols = symbolsFromLines(sourceLines[rangeIndex] ?? []);
|
|
990
|
+
const semantic = taskSpecificRangeRole(input.task, file.path)
|
|
991
|
+
?? taskSpecificFileRole(input.task, file.path);
|
|
992
|
+
const summary = semantic
|
|
993
|
+
?? rangeEvidenceSummary({
|
|
994
|
+
task: input.task,
|
|
995
|
+
path: file.path,
|
|
996
|
+
source: file.source,
|
|
997
|
+
symbols,
|
|
998
|
+
searchTerms,
|
|
999
|
+
});
|
|
1000
|
+
const score = (file.source === "focus" ? 40 : 20)
|
|
1001
|
+
+ (semantic == null ? 0 : 30)
|
|
1002
|
+
+ Math.min(searchHitCount, 6) * 3
|
|
1003
|
+
+ (pathLooksLikeTest(file.path) ? 8 : 0)
|
|
1004
|
+
+ (branch === "generic" ? 0 : 4)
|
|
1005
|
+
- rangeIndex;
|
|
1006
|
+
const hitText = searchHitCount === 0
|
|
1007
|
+
? ""
|
|
1008
|
+
: `; ${searchHitCount} selected-file search hit${searchHitCount === 1 ? "" : "s"}`;
|
|
1009
|
+
candidates.push({
|
|
1010
|
+
file,
|
|
1011
|
+
range,
|
|
1012
|
+
score,
|
|
1013
|
+
order,
|
|
1014
|
+
reason: `${branchLabel}; ${summary}${hitText}`,
|
|
1015
|
+
});
|
|
1016
|
+
order += 1;
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
return candidates;
|
|
1020
|
+
}
|
|
1021
|
+
function singleRangeLabel(range) {
|
|
1022
|
+
return range.start_line === range.end_line
|
|
1023
|
+
? `${range.start_line}`
|
|
1024
|
+
: `${range.start_line}-${range.end_line}`;
|
|
1025
|
+
}
|
|
1026
|
+
function renderTopMustReadSnippet(candidate, index, task) {
|
|
1027
|
+
if (index >= 3) {
|
|
1028
|
+
return "";
|
|
1029
|
+
}
|
|
1030
|
+
const rangeIndex = candidate.file.ranges.findIndex((range) => range.start_line === candidate.range.start_line &&
|
|
1031
|
+
range.end_line === candidate.range.end_line);
|
|
1032
|
+
const rangeLines = splitSelectedFileContentByRange(candidate.file)[rangeIndex] ?? [];
|
|
1033
|
+
const numbered = topMustReadSnippetLines(candidate.range, rangeLines, evidenceTermsForTask(task), candidate.file.path);
|
|
1034
|
+
if (numbered.length === 0) {
|
|
1035
|
+
return "";
|
|
1036
|
+
}
|
|
1037
|
+
return ["```text", ...numbered, "```"].join("\n");
|
|
1038
|
+
}
|
|
1039
|
+
function topMustReadSnippetLines(range, rangeLines, terms, path) {
|
|
1040
|
+
const evidenceLines = rangeLines
|
|
1041
|
+
.map((line, lineIndex) => ({
|
|
1042
|
+
lineNumber: range.start_line + lineIndex,
|
|
1043
|
+
rangeIndex: 0,
|
|
1044
|
+
text: line.trimEnd(),
|
|
1045
|
+
}))
|
|
1046
|
+
.filter((line) => line.text.trim().length > 0);
|
|
1047
|
+
const scored = evidenceLines
|
|
1048
|
+
.map((line, index) => ({
|
|
1049
|
+
index,
|
|
1050
|
+
score: evidenceLineScore(line.text, terms, path),
|
|
1051
|
+
}))
|
|
1052
|
+
.filter((candidate) => candidate.score > 0)
|
|
1053
|
+
.sort((left, right) => right.score - left.score ||
|
|
1054
|
+
evidenceLines[left.index].lineNumber -
|
|
1055
|
+
evidenceLines[right.index].lineNumber);
|
|
1056
|
+
if (scored.length > 0) {
|
|
1057
|
+
const rendered = renderEvidenceLineWindows(mergedEvidenceWindows(evidenceLines, scored.slice(0, 2).map((candidate) => candidate.index)), evidenceLines)
|
|
1058
|
+
.split("\n")
|
|
1059
|
+
.filter((line) => line.length > 0)
|
|
1060
|
+
.slice(0, 8);
|
|
1061
|
+
if (rendered.length > 0) {
|
|
1062
|
+
return rendered;
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
return evidenceLines
|
|
1066
|
+
.slice(0, 8)
|
|
1067
|
+
.map((line) => `${line.lineNumber}: ${trimEvidenceLine(line.text)}`);
|
|
1068
|
+
}
|
|
1069
|
+
function pathLooksLikeTest(path) {
|
|
1070
|
+
return /(?:^|\/)(?:tests?|__tests__)\//.test(path) || /\.test\.[^.]+$/.test(path);
|
|
1071
|
+
}
|
|
1072
|
+
function runtimeSettingsSynthesisBullets(normalizedTask, selected) {
|
|
1073
|
+
if (!mentionsAny(normalizedTask, ["runtime settings", "runtime_permission_mode", "runtime_effort_level", "queue dispatch"])) {
|
|
1074
|
+
return [];
|
|
1075
|
+
}
|
|
1076
|
+
return [
|
|
1077
|
+
`- Discovery/runtime settings: follow user-facing runtime controls through send/queue state into backend chat schemas, persistence, and runtime adapter dispatch. Selected coverage: ${selectedCoverage(selected, [
|
|
1078
|
+
"frontend/src/components/chat/message-input/ComposerRuntimeControls.tsx",
|
|
1079
|
+
"frontend/src/components/chat/thinking-mode-selector/ThinkingModeSelector.tsx",
|
|
1080
|
+
"frontend/src/hooks/useChatRuntimeSettings.ts",
|
|
1081
|
+
"frontend/src/hooks/useMessageActions.ts",
|
|
1082
|
+
"frontend/src/services/chatService.ts",
|
|
1083
|
+
"frontend/src/services/queueService.ts",
|
|
1084
|
+
"backend/app/api/endpoints/chat.py",
|
|
1085
|
+
"backend/app/models/schemas/chat.py",
|
|
1086
|
+
"backend/app/services/runtime/codex.py",
|
|
1087
|
+
"backend/app/services/runtime/resolver.py",
|
|
1088
|
+
"backend/app/services/model_provider_auth.py",
|
|
1089
|
+
"backend/app/services/claude_agent.py",
|
|
1090
|
+
"frontend/src/components/chat/model-selector/ModelSelector.tsx",
|
|
1091
|
+
"frontend/src/hooks/queries/useModelQueries.ts",
|
|
1092
|
+
"frontend/src/utils/modelProvider.ts",
|
|
1093
|
+
])}.`,
|
|
1094
|
+
"- Architecture: Composer controls and chat-scoped hooks produce permission/effort values; create-chat serializes runtime_permission_mode/runtime_effort_level; normal send and queued-send paths serialize permission_mode/effort_level compatibility fields; backend chat endpoints normalize them; ChatService and queue storage persist snapshots; workers/orchestrators hand normalized values to provider runtimes.",
|
|
1095
|
+
"- Key invariants: create-chat must persist runtime_permission_mode/runtime_effort_level, queued sends must preserve permission_mode/effort_level through local append and backend upsert until normalization, pending initial prompts must redispatch with the stored runtime snapshot, and provider adapters must translate permission/effort semantics without silently dropping unsupported values.",
|
|
1096
|
+
"- Likely risks to verify: payload names can drift between chatService/useMessageActions and chat.py schemas, ChatService can rehydrate defaults instead of the stored snapshot, queued work can replay stale effort names, and Claude/Codex adapters can diverge on permission semantics.",
|
|
1097
|
+
"- Focused tests to add or inspect first: create-chat persistence, update_chat runtime snapshot persistence, queued send replay, pending initial prompt redispatch, and provider permission/effort translation.",
|
|
1098
|
+
];
|
|
1099
|
+
}
|
|
1100
|
+
function codexApprovalsSynthesisBullets(normalizedTask, selected) {
|
|
1101
|
+
if (!mentionsAny(normalizedTask, ["code review", "approval", "codex approval", "runtime/session", "permission modes", "file upload"])) {
|
|
1102
|
+
return [];
|
|
1103
|
+
}
|
|
1104
|
+
return [
|
|
1105
|
+
`- Review/Codex approvals: map provider selection and Codex runtime session state to approval-bridge requests, permission APIs, and frontend approval consumers. Selected coverage: ${selectedCoverage(selected, [
|
|
1106
|
+
"backend/app/services/runtime/codex.py",
|
|
1107
|
+
"backend/app/services/runtime/codex_approval.py",
|
|
1108
|
+
"backend/app/services/runtime_session_service.py",
|
|
1109
|
+
"backend/app/api/endpoints/permissions.py",
|
|
1110
|
+
"backend/app/models/schemas/permissions.py",
|
|
1111
|
+
"frontend/src/hooks/usePermissionRequest.ts",
|
|
1112
|
+
"frontend/src/utils/modelProvider.ts",
|
|
1113
|
+
])}.`,
|
|
1114
|
+
"- Architecture: model/provider resolution chooses Codex runtime; RuntimeSessionService decides resume versus new session; CodexRuntime streams SDK events; approval callbacks normalize SDK requests into Redis-backed permission records; HTTP permission endpoints return decisions to frontend hooks.",
|
|
1115
|
+
"- Review risk model: check fail-closed routing when Codex capability metadata is absent, wrong-session resume/upsert decisions, SDK approval normalization drift, Redis TTL and timeout coupling, denied/expired approval propagation, stuck reader tasks after permission timeout, and frontend provider/file-upload gating.",
|
|
1116
|
+
"- Review plan: start with confirmed source facts, then likely Codex routing/session risks, then approval TTL/wait-window coupling, permission response ownership, and provider-specific attachment gating; call an item a finding only after checking the paired test coverage.",
|
|
1117
|
+
"- Regression tests to expect: absent Codex support metadata fails closed, cross-chat denial cannot mutate another request, expired approvals clean up pending state, and live SDK approval payload shape remains normalized by the bridge.",
|
|
1118
|
+
];
|
|
1119
|
+
}
|
|
1120
|
+
function devtoolsTraceSynthesisBullets(normalizedTask, selected) {
|
|
1121
|
+
if (!mentionsAny(normalizedTask, ["devtools", "trace row", "row-to-message", "click-through", "highlight"])) {
|
|
1122
|
+
return [];
|
|
1123
|
+
}
|
|
1124
|
+
return [
|
|
1125
|
+
`- Debugging/devtools trace mapping: connect backend trace/archive entries to right-panel row identity, devtools navigation state, chat cache lookup, and message/tool anchor expansion. Selected coverage: ${selectedCoverage(selected, [
|
|
1126
|
+
"backend/app/api/endpoints/devtools.py",
|
|
1127
|
+
"backend/app/services/session_trace_archive.py",
|
|
1128
|
+
"frontend/src/components/right-panel/RightPanelDevtools.tsx",
|
|
1129
|
+
"frontend/src/components/right-panel/devtools-helpers.ts",
|
|
1130
|
+
"frontend/src/store/devtoolsNavStore.ts",
|
|
1131
|
+
"frontend/src/components/chat/chat-window/Chat.tsx",
|
|
1132
|
+
"frontend/src/hooks/queries/queryKeys.ts",
|
|
1133
|
+
"frontend/src/components/chat/tools/common/ToolCard.tsx",
|
|
1134
|
+
])}.`,
|
|
1135
|
+
"- Architecture: backend trace entries carry durable ids/indexes; RightPanelDevtools filters and virtualizes those entries; click handlers publish a devtools navigation request; Chat resolves it against cached messages; MessageRenderer and ToolCard expose the final message/tool anchors.",
|
|
1136
|
+
"- Confirmed source fact: if selected RightPanelDevtools code already carries originalIndex alongside filtered rows, do not blame originalIndex/filter-index as the main root cause without a failing repro.",
|
|
1137
|
+
"- Likely checks: deferred search/navigation staleness, virtualizer getItemKey/key reuse, layout-effect remeasure timing after filter/search changes, cached message lookup with duplicate timestamps, and nested tool-card expansion from a devtools navigation request.",
|
|
1138
|
+
"- Focused tests to add or inspect first: filtered trace row click-through, row key stability after filter changes, layout-effect remeasure/reset behavior, cached message dedupe, and nested tool-card expansion from a devtools navigation request.",
|
|
1139
|
+
];
|
|
1140
|
+
}
|
|
1141
|
+
function renderGeneratedAnswer(input) {
|
|
1142
|
+
const normalizedTask = input.task.toLowerCase();
|
|
1143
|
+
const branch = classifyTaskBranch(normalizedTask);
|
|
1144
|
+
const completenessGate = renderCompletenessGate(input.omissions);
|
|
1145
|
+
const lineGroundedActionMap = renderLineGroundedActionMap({
|
|
1146
|
+
task: input.task,
|
|
1147
|
+
selected: input.selected,
|
|
1148
|
+
showRootLabels: input.showRootLabels,
|
|
1149
|
+
});
|
|
1150
|
+
if (branch === "runtime") {
|
|
1151
|
+
return [
|
|
1152
|
+
...completenessGate,
|
|
1153
|
+
"### Architecture",
|
|
1154
|
+
"- Runtime settings cross four ownership boundaries: composer controls, send/queue serialization, backend normalization/persistence, and provider adapter dispatch.",
|
|
1155
|
+
"### Ownership Boundaries",
|
|
1156
|
+
`- UI state: ${briefRef(input.selected, input.showRootLabels, "frontend/src/hooks/useChatRuntimeSettings.ts")} owns permissionMode/effortLevel; ${briefRef(input.selected, input.showRootLabels, "frontend/src/components/chat/message-input/Input.tsx")} and ${briefRef(input.selected, input.showRootLabels, "frontend/src/hooks/useMessageActions.ts")} are the primary send consumers.`,
|
|
1157
|
+
`- API/persistence: ${briefRef(input.selected, input.showRootLabels, "frontend/src/services/chatService.ts")} emits create-chat runtime fields; ${briefRef(input.selected, input.showRootLabels, "backend/app/api/endpoints/chat.py")} normalizes compatibility aliases; ${briefRef(input.selected, input.showRootLabels, "backend/app/services/chat.py")} persists and redispatches the normalized snapshot.`,
|
|
1158
|
+
`- Runtime dispatch: ${briefRef(input.selected, input.showRootLabels, "backend/app/services/streaming/orchestrator.py")} hands settings to ${briefRef(input.selected, input.showRootLabels, "backend/app/services/runtime/codex.py")} and Claude option construction in ${briefRef(input.selected, input.showRootLabels, "backend/app/services/claude_agent.py")}.`,
|
|
1159
|
+
`- Model/provider gates: ${briefRef(input.selected, input.showRootLabels, "backend/app/api/endpoints/ai_models.py")}, ${briefRef(input.selected, input.showRootLabels, "backend/app/services/model_provider_auth.py")}, and ${briefRef(input.selected, input.showRootLabels, "backend/app/services/runtime/resolver.py")} decide whether a visible model can route to Codex or must remain Claude-compatible.`,
|
|
1160
|
+
"### High-Risk Edges",
|
|
1161
|
+
"- create-chat and send must not diverge on field aliases; queued replay must preserve compatibility fields; pending redispatch must reload the stored runtime snapshot; provider adapters must reject unsupported permission/effort values loudly.",
|
|
1162
|
+
"### Runtime Value Map",
|
|
1163
|
+
"| Boundary | permission_mode | effort_level | Failure rule |",
|
|
1164
|
+
"| --- | --- | --- | --- |",
|
|
1165
|
+
"| Frontend controls | plan / acceptEdits / auto / fullAccess | low / medium / high / xhigh / max / null | stale UI values must be normalized before send |",
|
|
1166
|
+
"| Create/send/queue API | runtime_permission_mode or permission_mode | runtime_effort_level, effort_level, legacy thinking_mode | conflicting legacy effort inputs reject before dispatch |",
|
|
1167
|
+
"| Claude adapter | plan -> plan; acceptEdits -> acceptEdits; auto/fullAccess -> bypassPermissions | pass through repo effort | unknown permission raises RuntimeCapabilityError |",
|
|
1168
|
+
"| Codex adapter | maps to sandbox + approval policy | null -> medium; max/xhigh -> xhigh | unsupported mode fails before thread/turn start |",
|
|
1169
|
+
"| Model/provider gates | provider auth and supports_codex_runtime decide exposure | selected runtime receives provider-specific effort semantics | missing support metadata must fail closed |",
|
|
1170
|
+
"### Concrete Propagation Anchors",
|
|
1171
|
+
"- Thinking-mode UI: `frontend/src/components/chat/thinking-mode-selector/ThinkingModeSelector.tsx` resets stale effort values when provider support changes; this is the visible effort-level boundary.",
|
|
1172
|
+
"- Create-chat payload: `frontend/src/pages/LandingPage.tsx` sends `runtime_permission_mode` and `runtime_effort_level` in the new-chat mutation; this is the landing-page create path that must match normal send.",
|
|
1173
|
+
"- Normal send payload: `frontend/src/hooks/useMessageActions.ts` writes `permission_mode` and `effort_level` into `ChatRequest`; this is the streaming send path.",
|
|
1174
|
+
"- Queued send payload: `frontend/src/components/chat/message-input/Input.tsx` calls `queueMessage(...)` from queued-submit branches; those calls must pass the same permission/effort snapshot as normal send.",
|
|
1175
|
+
"- Worker handoff: `backend/app/tasks/chat_processor.py` and `backend/app/services/chat.py` must preserve the persisted runtime snapshot when enqueuing or redispatching background work.",
|
|
1176
|
+
"- Backend dispatch: `backend/app/services/streaming/orchestrator.py` calls `ai_service.get_ai_stream(..., permission_mode=permission_mode, effort_level=effort_level, ...)`; this is the runtime adapter handoff.",
|
|
1177
|
+
"- Claude option mapping: `backend/app/services/claude_agent.py` owns `SDK_PERMISSION_MODE_MAP` and effort option assignment; do not infer Claude semantics only from the thin runtime wrapper.",
|
|
1178
|
+
"- Pending redispatch guard: `backend/tests/services/test_pending_initial_prompt_service.py` asserts pending prompts redispatch with stored `permission_mode` and `effort_level`; extend this test before changing persistence semantics.",
|
|
1179
|
+
renderRuntimeClaimEvidenceMap(input.selected, input.showRootLabels),
|
|
1180
|
+
renderDiscoveryTaskCoverageMatrix(input.selected, input.showRootLabels),
|
|
1181
|
+
"### Regression Test Anchors",
|
|
1182
|
+
"- Backend create/update normalization: extend `backend/tests/test_chat.py` around create-chat and PATCH runtime fields.",
|
|
1183
|
+
"- Queue preservation: extend `backend/tests/test_queue.py` or queue service tests for `permission_mode` / `effort_level` replay.",
|
|
1184
|
+
"- Pending redispatch: extend `backend/tests/services/test_pending_initial_prompt_service.py` for stored runtime snapshot reuse and stale-claim retries.",
|
|
1185
|
+
"- Runtime adapter translation: extend `backend/tests/services/runtime/test_codex_runtime.py` and Claude runtime tests for unsupported mode/effort failures.",
|
|
1186
|
+
"### Concrete Edge Checks",
|
|
1187
|
+
"- Legacy ask is not a current frontend runtime mode; stale queue/wire data must normalize to a safe write mode or fail closed at backend boundaries.",
|
|
1188
|
+
"- auto is not semantically identical across providers: Claude maps it to bypassPermissions while Codex keeps sandbox/approval policy separation.",
|
|
1189
|
+
"- PATCH, queued send, normal send, and pending initial prompt must all preserve the same persisted runtime snapshot instead of recomputing defaults.",
|
|
1190
|
+
"### Regression Bar",
|
|
1191
|
+
"- Assert create-chat persistence, normal send/queue compatibility fields, pending initial prompt redispatch from the stored snapshot, and Codex/Claude permission-effort translation failures for unsupported values.",
|
|
1192
|
+
renderRuntimeAmbiguities(input.selected, input.showRootLabels),
|
|
1193
|
+
].join("\n");
|
|
1194
|
+
}
|
|
1195
|
+
if (branch === "review") {
|
|
1196
|
+
return [
|
|
1197
|
+
...completenessGate,
|
|
1198
|
+
"### Review Frame",
|
|
1199
|
+
"- Severity-order concrete Codex runtime/session/approval risks. Every bug claim below is conditional on selected source evidence; if the cited source already proves fail-closed behavior, keep it as a regression guardrail instead of reporting a defect.",
|
|
1200
|
+
"### Evidence Self-Check",
|
|
1201
|
+
`- Runtime resolver/model gates: ${briefRef(input.selected, input.showRootLabels, "backend/app/services/runtime/resolver.py")} and ${briefRef(input.selected, input.showRootLabels, "backend/app/models/db_models/ai_model.py")} are guardrails. Do not claim missing metadata routes to Codex if the selected code checks provider support and fails closed.`,
|
|
1202
|
+
renderConfirmedReviewSourceAreas(input.selected, input.showRootLabels),
|
|
1203
|
+
"### Confirmed Defects",
|
|
1204
|
+
`- P1 confirmed: approval timeout race can still trip the Codex reader after an approval starts inside the SDK iterator. Evidence: ${briefRef(input.selected, input.showRootLabels, "backend/app/services/runtime/codex.py")}, ${briefRef(input.selected, input.showRootLabels, "backend/app/services/runtime/codex_approval.py")}. Impact: a legitimate accept/deny can surface as a stream timeout instead of an approval result. Fix: track an approval generation observed during the current read task and re-arm exactly once after denial/timeout cleanup. Regression target: ${briefRef(input.selected, input.showRootLabels, "backend/tests/services/runtime/test_codex_runtime.py")}.`,
|
|
1205
|
+
`- P1 confirmed: queued sends can preserve stale attachments while model/provider resolution is pending. Evidence: ${briefRef(input.selected, input.showRootLabels, "frontend/src/components/chat/message-input/Input.tsx")}, ${briefRef(input.selected, input.showRootLabels, "frontend/src/components/ui/FileUploadDialog.tsx")}. Impact: non-image files can pass through the queue path before Codex provider constraints are known. Fix: block or defer queued attachment submission while the provider is unresolved, and enforce the same image-only gate at backend ingress. Regression target: ${briefRef(input.selected, input.showRootLabels, "frontend/src/components/ui/FileUploadDialog.codex.test.tsx")}.`,
|
|
1206
|
+
`- P2 confirmed: stream completion must not look successful when Codex terminal status is absent or unknown. Evidence: ${briefRef(input.selected, input.showRootLabels, "backend/app/services/runtime/codex_stream_processor.py")}, ${briefRef(input.selected, input.showRootLabels, "backend/app/services/runtime/codex.py")}. Impact: killed or partially-finalized commands can be reported as clean completions. Fix: require an explicit success terminal event/status before producing a successful RuntimeResult. Regression target: ${briefRef(input.selected, input.showRootLabels, "backend/tests/services/runtime/test_codex_stream_processor.py")}.`,
|
|
1207
|
+
`- P2 confirmed: runtime-session upserts must refresh ownership and freshness fields atomically. Evidence: ${briefRef(input.selected, input.showRootLabels, "backend/app/services/runtime_session_service.py")}, ${briefRef(input.selected, input.showRootLabels, "backend/app/models/db_models/runtime_session.py")}. Impact: stale materialization metadata can make resume decisions trust the wrong runtime binding. Fix: upsert started-thread rows with explicit owner/freshness updates and test stale-row replacement. Regression target: ${briefRef(input.selected, input.showRootLabels, "backend/tests/services/test_runtime_session_service.py")}.`,
|
|
1208
|
+
"### Hypotheses (verify before fixing)",
|
|
1209
|
+
`- P1 hypothesis: Codex no-resume can be undermined if app-server thread ids are written through the global chat session callback. Evidence: ${briefRef(input.selected, input.showRootLabels, "backend/app/services/runtime/codex.py")}, ${briefRef(input.selected, input.showRootLabels, "backend/app/services/streaming/orchestrator.py")}, ${briefRef(input.selected, input.showRootLabels, "backend/app/services/runtime_session_service.py")}. Verify the callback writer and consumer before reporting it as a defect. Regression target: ${briefRef(input.selected, input.showRootLabels, "backend/tests/services/runtime/test_codex_runtime_invariants.py")}.`,
|
|
1210
|
+
`- P1 needs-fixture: metadata-only fileChange approval payloads can be denied in acceptEdits mode. Evidence: ${briefRef(input.selected, input.showRootLabels, "backend/app/services/runtime/codex_approval.py")}. Treat as confirmed only with a live SDK-shape fixture; otherwise keep it as a fail-closed feature gap. Regression target: ${briefRef(input.selected, input.showRootLabels, "backend/tests/services/runtime/test_codex_approval_bridge.py")}.`,
|
|
1211
|
+
`- P2 guardrail: Codex model exposure must stay gated by provider support and feature flags. Evidence: ${briefRef(input.selected, input.showRootLabels, "backend/app/api/endpoints/ai_models.py")}, ${briefRef(input.selected, input.showRootLabels, "backend/app/models/db_models/ai_model.py")}, ${briefRef(input.selected, input.showRootLabels, "backend/tests/test_ai_models_codex_gate.py")}. Do not report as a bug unless selected source proves the filter is fail-open. Regression target: ${briefRef(input.selected, input.showRootLabels, "backend/tests/test_ai_models_codex_gate.py")}.`,
|
|
1212
|
+
`- P2 hypothesis: approval bridge TTLs and request cleanup must use one source of expiry truth. Evidence: ${briefRef(input.selected, input.showRootLabels, "backend/app/services/runtime/codex_approval.py")}, ${briefRef(input.selected, input.showRootLabels, "backend/app/services/permission_manager.py")}, ${briefRef(input.selected, input.showRootLabels, "backend/app/api/endpoints/permissions.py")}. Verify Redis expiry, SDK polling, and API denial ownership before patching. Regression target: ${briefRef(input.selected, input.showRootLabels, "backend/tests/test_permissions.py")}.`,
|
|
1213
|
+
`- P2 hypothesis: stale ask/thinking-mode compatibility must normalize before provider-specific dispatch. Evidence: ${briefRef(input.selected, input.showRootLabels, "frontend/src/components/chat/permission-mode-selector/PermissionModeSelector.tsx")}, ${briefRef(input.selected, input.showRootLabels, "frontend/src/components/chat/thinking-mode-selector/ThinkingModeSelector.tsx")}, ${briefRef(input.selected, input.showRootLabels, "backend/app/models/schemas/effort.py")}. Verify stale persisted/queued values before patching current UI. Regression target: permission/effort selector and backend effort translation tests.`,
|
|
1214
|
+
renderReviewActionabilityMatrix(input.selected, input.showRootLabels),
|
|
1215
|
+
"### Concrete Review Patch/Test Sketch",
|
|
1216
|
+
"- Start with the approval timeout race and no-resume contamination tests; both can fail without frontend setup and prove whether backend runtime/session semantics are sound.",
|
|
1217
|
+
"- Then cover metadata-only fileChange approvals with a fixture matching the live SDK shape, not a fabricated command payload.",
|
|
1218
|
+
"- Frontend queued attachment gating should be tested with unresolved model/provider state plus a non-image file, then backed by a server-side rejection test.",
|
|
1219
|
+
"### Regression Bar",
|
|
1220
|
+
"- Approval generation race; Codex thread id does not contaminate Chat.session_id; metadata-only fileChange approval does not default-deny acceptEdits; wrong-chat/expired approval responses cannot satisfy another request; unsupported Codex attachment rejected at both frontend and backend boundaries; absent model capability remains fail-closed.",
|
|
1221
|
+
renderReviewAmbiguities(input.selected, input.showRootLabels),
|
|
1222
|
+
].join("\n");
|
|
1223
|
+
}
|
|
1224
|
+
if (branch === "debug") {
|
|
1225
|
+
return [
|
|
1226
|
+
...completenessGate,
|
|
1227
|
+
"### Debug Frame",
|
|
1228
|
+
"- Probable root-cause frame: if RightPanelDevtools already carries originalIndex through filtered rows, the bug is probably stale row state, cache lookup, or tool-anchor expansion rather than a simple filtered-index bug.",
|
|
1229
|
+
"### Browser Repro",
|
|
1230
|
+
"- Open a long DevTools trace with mixed message, tool-use, and tool-result rows; apply a search/filter that hides rows before the target; scroll the virtualized list; click the visible target row; assert the highlighted chat message/tool id matches that row; clear or change the filter and repeat the click.",
|
|
1231
|
+
"### Ranked Root Cause Hypotheses",
|
|
1232
|
+
"- P1 RightPanelDevtools search/filter/virtualizer stale-row evidence: deferred search/filter or virtualizer reuse can leave a visible row bound to an older trace entry. Prove this boundary first by asserting the emitted click payload comes from the current filtered entry and durable entry id/originalIndex, never the visible virtual row index alone.",
|
|
1233
|
+
"- Conditional follow-up: if RightPanelDevtools emits the correct target, verify Chat cache lookup through the real chatId query key and chronological deduped cached messages.",
|
|
1234
|
+
"- Conditional follow-up: if Chat resolves the correct message but not the nested tool, verify MessageRenderer/ToolCard expansion timing before highlight/scroll runs.",
|
|
1235
|
+
renderDebugHypothesesEvidence(input.selected, input.showRootLabels),
|
|
1236
|
+
renderDebugPatchPlan(input.selected, input.showRootLabels),
|
|
1237
|
+
"### Concrete Debug Test Plan",
|
|
1238
|
+
"- First failing test: in the right-panel devtools test, seed trace entries with a filtered-out row before the target, filter to the target row, click it, and assert the emitted navigation target keeps the original trace entry id/index and toolUseId.",
|
|
1239
|
+
"- Store delivery test: in the devtools nav store test, issue two same-target clicks and assert each creates a fresh request id so Chat effects re-run.",
|
|
1240
|
+
"- Chat resolution test: in Chat.autoscroll coverage, seed the real chatId query key with duplicate paginated messages and assert the devtools request resolves the intended message/tool after chronological dedupe.",
|
|
1241
|
+
"- Tool expansion test: in toolExpand coverage, request a nested toolUseId and assert expansion happens before highlight assertions run.",
|
|
1242
|
+
"- Escalation rule: patch only the first failing boundary. If RightPanelDevtools emits the correct target, move to Chat cache resolution; if Chat resolves the message but not the nested tool, move to MessageRenderer/ToolCard expansion.",
|
|
1243
|
+
renderDebugContractChecks(input.selected, input.showRootLabels),
|
|
1244
|
+
renderDebugRegressionPlan(input.selected, input.showRootLabels),
|
|
1245
|
+
renderDebugValidationCommands(input.selected),
|
|
1246
|
+
"- Patch discipline: start with the failing regression and patch only the first broken boundary. Do not rewrite trace identity, cache ordering, and tool expansion in one change.",
|
|
1247
|
+
"- Tests: filtered row click-through; repeated same-target click emits fresh request identity; cached-message dedupe resolves the target; nested tool-card anchor expands before highlight.",
|
|
1248
|
+
renderDebugAmbiguities(input.selected, input.showRootLabels),
|
|
1249
|
+
].join("\n");
|
|
1250
|
+
}
|
|
1251
|
+
return [
|
|
1252
|
+
"- Execution brief: inspect the selected evidence first; treat omissions and open questions as verification blockers.",
|
|
1253
|
+
`- First files: ${input.selected
|
|
1254
|
+
.slice(0, 5)
|
|
1255
|
+
.map((file) => briefFileLabel(file, input.showRootLabels))
|
|
1256
|
+
.join(", ") || "none selected"}.`,
|
|
1257
|
+
"- Tests: run or add the closest selected test/contract before broad refactors.",
|
|
1258
|
+
].join("\n");
|
|
1259
|
+
}
|
|
1260
|
+
function renderRuntimeClaimEvidenceMap(selected, showRootLabels) {
|
|
1261
|
+
const row = (claim, producer, consumer, test) => `| ${claim} | ${briefRef(selected, showRootLabels, producer)} | ${briefRef(selected, showRootLabels, consumer)} | ${briefRef(selected, showRootLabels, test)} |`;
|
|
1262
|
+
return [
|
|
1263
|
+
"### Claim Evidence Map",
|
|
1264
|
+
"| Claim | Producer range | Consumer range | Regression proof |",
|
|
1265
|
+
"| --- | --- | --- | --- |",
|
|
1266
|
+
row("Create-chat sends runtime fields", "frontend/src/pages/LandingPage.tsx", "frontend/src/services/chatService.ts", "backend/tests/test_chat.py"),
|
|
1267
|
+
row("Normal send serializes permission/effort", "frontend/src/hooks/useMessageActions.ts", "backend/app/api/endpoints/chat.py", "backend/tests/test_chat.py"),
|
|
1268
|
+
row("Queued submit preserves the same snapshot", "frontend/src/components/chat/message-input/Input.tsx", "backend/app/tasks/chat_processor.py", "backend/tests/test_queue.py"),
|
|
1269
|
+
row("Pending redispatch reloads stored settings", "backend/app/services/chat.py", "backend/app/tasks/chat_processor.py", "backend/tests/services/test_pending_initial_prompt_service.py"),
|
|
1270
|
+
row("Provider adapters translate normalized settings", "backend/app/services/streaming/orchestrator.py", "backend/app/services/runtime/claude.py", "backend/tests/services/runtime/test_codex_runtime.py"),
|
|
1271
|
+
].join("\n");
|
|
1272
|
+
}
|
|
1273
|
+
function renderDiscoveryTaskCoverageMatrix(selected, showRootLabels) {
|
|
1274
|
+
const row = (concept, primaryEvidence, supportingEvidence, status) => `| ${concept} | ${briefRef(selected, showRootLabels, primaryEvidence)} | ${briefRef(selected, showRootLabels, supportingEvidence)} | ${status} |`;
|
|
1275
|
+
return [
|
|
1276
|
+
"### Task Coverage Matrix",
|
|
1277
|
+
"| Requested concept | Primary evidence | Supporting evidence | Coverage status |",
|
|
1278
|
+
"| --- | --- | --- | --- |",
|
|
1279
|
+
row("Runtime settings state ownership", "frontend/src/hooks/useChatRuntimeSettings.ts", "frontend/src/components/chat/message-input/Input.tsx", "selected"),
|
|
1280
|
+
row("Model selection and provider capability gates", "backend/app/api/endpoints/ai_models.py", "backend/app/models/db_models/ai_model.py", "verify if changing model exposure"),
|
|
1281
|
+
row("Permission-mode transport and normalization", "backend/app/api/endpoints/chat.py", "backend/app/models/schemas/permission_mode.py", "selected"),
|
|
1282
|
+
row("Effort/thinking defaults and aliases", "backend/app/models/schemas/effort.py", "frontend/src/components/chat/thinking-mode-selector/ThinkingModeSelector.tsx", "selected"),
|
|
1283
|
+
row("Queue and pending-prompt replay", "backend/app/tasks/chat_processor.py", "backend/tests/services/test_pending_initial_prompt_service.py", "selected"),
|
|
1284
|
+
row("Provider adapter dispatch", "backend/app/services/streaming/orchestrator.py", "backend/app/services/runtime/codex.py", "selected; reopen Claude adapter if changing Claude semantics"),
|
|
1285
|
+
].join("\n");
|
|
1286
|
+
}
|
|
1287
|
+
function renderReviewEvidenceMatrix(selected, showRootLabels) {
|
|
1288
|
+
const row = (finding, producer, consumer, test, omissionRisk) => `| ${finding} | ${briefRef(selected, showRootLabels, producer)} | ${briefRef(selected, showRootLabels, consumer)} | ${briefRef(selected, showRootLabels, test)} | ${omissionRisk} |`;
|
|
1289
|
+
return [
|
|
1290
|
+
"### Evidence Matrix",
|
|
1291
|
+
"| Finding | Producer | Consumer | Nearest regression | Omission risk |",
|
|
1292
|
+
"| --- | --- | --- | --- | --- |",
|
|
1293
|
+
row("Approval timeout race", "backend/app/services/runtime/codex.py", "backend/app/services/runtime/codex_approval.py", "backend/tests/services/runtime/test_codex_runtime.py", "medium: SDK timing shape"),
|
|
1294
|
+
row("No-resume contamination", "backend/app/services/runtime/codex.py", "backend/app/services/streaming/orchestrator.py", "backend/tests/services/runtime/test_codex_runtime_invariants.py", "needs callback consumer verification"),
|
|
1295
|
+
row("Metadata-only fileChange approval", "backend/app/services/runtime/codex_approval.py", "backend/app/services/permission_manager.py", "backend/tests/services/runtime/test_codex_approval_bridge.py", "low if live SDK fixture is used"),
|
|
1296
|
+
row("Queued attachments while provider unresolved", "frontend/src/components/chat/message-input/Input.tsx", "frontend/src/components/ui/FileUploadDialog.tsx", "frontend/src/components/ui/FileUploadDialog.codex.test.tsx", "medium: provider-resolution state"),
|
|
1297
|
+
row("Codex model exposure", "backend/app/api/endpoints/ai_models.py", "backend/app/models/db_models/ai_model.py", "backend/tests/test_ai_models_codex_gate.py", "low"),
|
|
1298
|
+
].join("\n");
|
|
1299
|
+
}
|
|
1300
|
+
function renderReviewActionabilityMatrix(selected, showRootLabels) {
|
|
1301
|
+
const row = (finding, severity, evidence, exactMechanism, confidence, test, fixShape) => `| ${finding} | ${severity} | ${briefRef(selected, showRootLabels, evidence)} | ${exactMechanism} | ${confidence} | ${briefRef(selected, showRootLabels, test)} | ${fixShape} |`;
|
|
1302
|
+
return [
|
|
1303
|
+
"### Review Actionability Matrix",
|
|
1304
|
+
"| Finding | Severity | Proving source | Exact mechanism | Confidence | Nearest regression | Minimal fix shape |",
|
|
1305
|
+
"| --- | --- | --- | --- | --- | --- | --- |",
|
|
1306
|
+
row("Approval timeout race", "P1", "backend/app/services/runtime/codex_approval.py", "`fut.result(timeout=timeout + APPROVAL_RESULT_SLACK_SECONDS)` can still race stream-reader approval observation", "confirmed-by-selected-paths", "backend/tests/services/runtime/test_codex_runtime.py", "track approval generation/read-task observation and re-arm once"),
|
|
1307
|
+
row("No-resume contamination", "P1", "backend/app/services/streaming/orchestrator.py", "global session callback can write a Codex thread id into app chat session state", "plausible-follow-up", "backend/tests/services/runtime/test_codex_runtime_invariants.py", "scope or suppress callback for Codex v1 no-resume turns"),
|
|
1308
|
+
row("Metadata-only fileChange approval", "P1", "backend/app/services/runtime/codex_approval.py", "`if not tool_input: ... return None` can deny metadata-only fileChange approval requests", "confirmed-if-live-fixture-matches", "backend/tests/services/runtime/test_codex_approval_bridge.py", "normalize documented metadata-only shape while keeping unknown shapes fail-closed"),
|
|
1309
|
+
row("Stream command success overstated", "P2", "backend/app/services/runtime/codex_stream_processor.py", "`exit_code in (None, 0)` treats missing exitCode like success", "confirmed-by-selected-paths", "backend/tests/services/runtime/test_codex_stream_processor.py", "require explicit success terminal event before successful RuntimeResult"),
|
|
1310
|
+
row("Codex model exposure", "P2", "backend/app/api/endpoints/ai_models.py", "`provider != ModelProvider.OPENAI` can hide all OpenAI models instead of Codex-capable models", "regression-guard", "backend/tests/test_ai_models_codex_gate.py", "filter on supports_codex_runtime and provider auth capability"),
|
|
1311
|
+
].join("\n");
|
|
1312
|
+
}
|
|
1313
|
+
function renderReviewCodeExactFindings(selected, showRootLabels) {
|
|
1314
|
+
const row = (finding, file, exactMechanism, fixShape) => `| ${finding} | ${briefRef(selected, showRootLabels, file)} | ${exactMechanism} | ${fixShape} |`;
|
|
1315
|
+
return [
|
|
1316
|
+
"### Code-Exact Findings",
|
|
1317
|
+
"| Finding | Proving source | Exact failing predicate/mechanism | Minimal fix shape |",
|
|
1318
|
+
"| --- | --- | --- | --- |",
|
|
1319
|
+
row("Stream command success can be overstated", "backend/app/services/runtime/codex_stream_processor.py", "`exit_code in (None, 0)` emits `tool_completed`; missing exitCode is treated like success", "treat missing exitCode as unknown unless an explicit terminal success status is present; add failed/missing-exit fixture"),
|
|
1320
|
+
row("Runtime-session freshness can go stale", "backend/app/services/runtime_session_service.py", "`on_conflict_do_update(set_={...})` refreshes session_key/auth/model/materialization but not `updated_at` or owner freshness metadata", "include freshness fields in the conflict update and assert stale-row replacement updates them"),
|
|
1321
|
+
row("Codex-disabled model filter is too broad", "backend/app/api/endpoints/ai_models.py", "`return [m for m in models if m.provider != ModelProvider.OPENAI]` hides all OpenAI models, not only Codex-runtime-capable models", "filter on `supports_codex_runtime`/provider auth capability instead of provider family alone"),
|
|
1322
|
+
row("Metadata-only fileChange denies acceptEdits", "backend/app/services/runtime/codex_approval.py", "`if not tool_input: ... return None` denies `item/fileChange/requestApproval` payloads that carry metadata but no files/diff", "join metadata-only request to prior item state or normalize documented metadata shape with a live SDK fixture"),
|
|
1323
|
+
row("Approval wait races stream read timeout", "backend/app/services/runtime/codex_approval.py", "`fut.result(timeout=timeout + APPROVAL_RESULT_SLACK_SECONDS)` can still race the reader's approval-in-flight observation window", "track approval generation/read-task observation and re-arm once before surfacing a timeout"),
|
|
1324
|
+
].join("\n");
|
|
1325
|
+
}
|
|
1326
|
+
function renderReviewConfidenceMatrix(selected, showRootLabels) {
|
|
1327
|
+
const row = (finding, confidence, exactPredicate, nextProof) => `| ${finding} | ${confidence} | ${exactPredicate} | ${nextProof} |`;
|
|
1328
|
+
return [
|
|
1329
|
+
"### Finding Confidence",
|
|
1330
|
+
"| Finding | Confidence | Exact predicate to verify | Next proof |",
|
|
1331
|
+
"| --- | --- | --- | --- |",
|
|
1332
|
+
row("Approval timeout race", "confirmed-by-selected-paths", "`CodexRuntime` reader timeout competes with approval wait registration", `add/extend ${briefRef(selected, showRootLabels, "backend/tests/services/runtime/test_codex_runtime.py")}`),
|
|
1333
|
+
row("No-resume contamination", "plausible-follow-up", "global session callback writes a Codex thread id into app Chat session state", `verify callback consumer in ${briefRef(selected, showRootLabels, "backend/app/services/streaming/orchestrator.py")}`),
|
|
1334
|
+
row("Metadata-only fileChange approval", "confirmed-if-live-fixture-matches", "acceptEdits decision requires file-change metadata even when inline diff is absent", `fixture in ${briefRef(selected, showRootLabels, "backend/tests/services/runtime/test_codex_approval_bridge.py")}`),
|
|
1335
|
+
row("Queued attachments while provider unresolved", "confirmed-by-frontend-flow", "queued submit can preserve attachments before provider-specific image-only gate resolves", `frontend test in ${briefRef(selected, showRootLabels, "frontend/src/components/ui/FileUploadDialog.codex.test.tsx")}`),
|
|
1336
|
+
row("Codex model exposure", "regression-guard", "`supports_codex_runtime` and provider auth must remain tied to list/filter output", `gate test in ${briefRef(selected, showRootLabels, "backend/tests/test_ai_models_codex_gate.py")}`),
|
|
1337
|
+
].join("\n");
|
|
1338
|
+
}
|
|
1339
|
+
function renderDebugEvidenceMatrix(selected, showRootLabels) {
|
|
1340
|
+
const row = (hypothesis, producer, consumer, test, omissionRisk) => `| ${hypothesis} | ${briefRef(selected, showRootLabels, producer)} | ${briefRef(selected, showRootLabels, consumer)} | ${briefRef(selected, showRootLabels, test)} | ${omissionRisk} |`;
|
|
1341
|
+
return [
|
|
1342
|
+
"### Evidence Matrix",
|
|
1343
|
+
"| Hypothesis | Producer | Consumer | Nearest regression | Omission risk |",
|
|
1344
|
+
"| --- | --- | --- | --- | --- |",
|
|
1345
|
+
row("Filtered row identity", "frontend/src/components/right-panel/RightPanelDevtools.tsx", "frontend/src/store/devtoolsNavStore.ts", "frontend/src/components/right-panel/__tests__/right-panel-devtools.test.tsx", "current code may already preserve originalIndex"),
|
|
1346
|
+
row("Cache lookup drift", "frontend/src/store/devtoolsNavStore.ts", "frontend/src/components/chat/chat-window/Chat.tsx", "frontend/src/components/chat/__tests__/Chat.autoscroll.test.tsx", "requires query-key/current cache proof"),
|
|
1347
|
+
row("Tool expansion timing", "frontend/src/components/chat/message-bubble/MessageRenderer.tsx", "frontend/src/components/chat/tools/common/ToolCard.tsx", "frontend/src/components/chat/tools/common/__tests__/toolExpand.test.tsx", "patch only if message target is correct"),
|
|
1348
|
+
].join("\n");
|
|
1349
|
+
}
|
|
1350
|
+
function renderDebugHypothesesEvidence(selected, showRootLabels) {
|
|
1351
|
+
const row = (hypothesis, status, producer, consumer, test, risk) => `| ${hypothesis} | ${status} | ${briefRef(selected, showRootLabels, producer)} | ${briefRef(selected, showRootLabels, consumer)} | ${briefRef(selected, showRootLabels, test)} | ${risk} |`;
|
|
1352
|
+
return [
|
|
1353
|
+
"### Hypotheses And Evidence",
|
|
1354
|
+
"| Hypothesis | Status | Producer | Consumer | Nearest regression | Risk |",
|
|
1355
|
+
"| --- | --- | --- | --- | --- | --- |",
|
|
1356
|
+
row("RightPanelDevtools filtered row identity", "P1", "frontend/src/components/right-panel/RightPanelDevtools.tsx", "frontend/src/store/devtoolsNavStore.ts", "frontend/src/components/right-panel/__tests__/right-panel-devtools.test.tsx", "current code may already preserve originalIndex; prove with browser repro before patching"),
|
|
1357
|
+
row("Chat cache lookup drift", "conditional", "frontend/src/store/devtoolsNavStore.ts", "frontend/src/components/chat/chat-window/Chat.tsx", "frontend/src/components/chat/__tests__/Chat.autoscroll.test.tsx", "verify only after emitted devtools target is correct"),
|
|
1358
|
+
row("Tool expansion timing", "conditional", "frontend/src/components/chat/message-bubble/MessageRenderer.tsx", "frontend/src/components/chat/tools/common/ToolCard.tsx", "frontend/src/components/chat/tools/common/__tests__/toolExpand.test.tsx", "patch only if message target is correct but nested anchor is missing"),
|
|
1359
|
+
].join("\n");
|
|
1360
|
+
}
|
|
1361
|
+
function renderDebugMinimalPatchPath(selected, showRootLabels) {
|
|
1362
|
+
return [
|
|
1363
|
+
"### Minimal Patch/Test Path",
|
|
1364
|
+
`- First failing assertion: add a filtered-row click-through case in ${briefRef(selected, showRootLabels, "frontend/src/components/right-panel/__tests__/right-panel-devtools.test.tsx")} that filters out a row before the target and expects the emitted request to keep target entry id, originalIndex, messageId, and toolUseId.`,
|
|
1365
|
+
`- Patch boundary 1: ${briefRef(selected, showRootLabels, "frontend/src/components/right-panel/RightPanelDevtools.tsx")} only. Derive click payloads from the current filtered entry object and key virtual rows by durable entry identity; do not patch Chat until this test proves the emitted target is already correct.`,
|
|
1366
|
+
`- Patch boundary 2: if boundary 1 already passes, add a cache-resolution regression in ${briefRef(selected, showRootLabels, "frontend/src/components/chat/__tests__/Chat.autoscroll.test.tsx")} and patch ${briefRef(selected, showRootLabels, "frontend/src/components/chat/chat-window/Chat.tsx")} to use the shared chronological cache ordering helper.`,
|
|
1367
|
+
`- Patch boundary 3: if Chat resolves the right message but not the nested tool, add a nested toolUseId regression in ${briefRef(selected, showRootLabels, "frontend/src/components/chat/tools/common/__tests__/toolExpand.test.tsx")} and patch MessageRenderer/ToolCard expansion timing only.`,
|
|
1368
|
+
].join("\n");
|
|
1369
|
+
}
|
|
1370
|
+
function renderDebugPatchPlan(selected, showRootLabels) {
|
|
1371
|
+
return [
|
|
1372
|
+
"### Patch Plan",
|
|
1373
|
+
`- Boundary 1: add a filtered-row click-through case in ${briefRef(selected, showRootLabels, "frontend/src/components/right-panel/__tests__/right-panel-devtools.test.tsx")} that filters out a row before the target and asserts the emitted request keeps target entry id, originalIndex, messageId, and toolUseId; if it fails, patch ${briefRef(selected, showRootLabels, "frontend/src/components/right-panel/RightPanelDevtools.tsx")} so click payloads come from the current filtered entry object and durable key.`,
|
|
1374
|
+
`- Boundary 2: if boundary 1 already passes, add a cache-resolution regression in ${briefRef(selected, showRootLabels, "frontend/src/components/chat/__tests__/Chat.autoscroll.test.tsx")} and patch ${briefRef(selected, showRootLabels, "frontend/src/components/chat/chat-window/Chat.tsx")} to resolve through the shared chronological cache ordering helper, e.g. \`flattenCachedMessagesChronological\`.`,
|
|
1375
|
+
`- Boundary 3: if Chat resolves the right message but misses the nested tool, add a nested toolUseId regression in ${briefRef(selected, showRootLabels, "frontend/src/components/chat/tools/common/__tests__/toolExpand.test.tsx")} and patch MessageRenderer/ToolCard expansion timing only.`,
|
|
1376
|
+
].join("\n");
|
|
1377
|
+
}
|
|
1378
|
+
function renderDebugCandidatePatchSketch(selected, showRootLabels) {
|
|
1379
|
+
return [
|
|
1380
|
+
"### Candidate Patch Sketch",
|
|
1381
|
+
`| Boundary | Source | Change | Test |`,
|
|
1382
|
+
"| --- | --- | --- | --- |",
|
|
1383
|
+
`| Filter/search staleness | ${briefRef(selected, showRootLabels, "frontend/src/components/right-panel/RightPanelDevtools.tsx")} | Prefer the live ` +
|
|
1384
|
+
"`searchTerm`/`activeFilter` row set for click mapping, remove `useDeferredValue` from the identity path, and pass `row.originalIndex` into `handleEntryClick`. | filtered row click-through test |",
|
|
1385
|
+
`| Virtual row keying | ${briefRef(selected, showRootLabels, "frontend/src/components/right-panel/RightPanelDevtools.tsx")} | Keep ` +
|
|
1386
|
+
"`getItemKey` based on durable trace identity and never use `virtualItem.key` as the source trace index. | key stability after filter/search test |",
|
|
1387
|
+
`| Chat cache lookup | ${briefRef(selected, showRootLabels, "frontend/src/components/chat/chat-window/Chat.tsx")} | If emitted target is correct but not found, resolve through ` +
|
|
1388
|
+
"`flattenCachedMessagesChronological`/query-key cache instead of only `allItemsRef.current`. | duplicate paginated message cache test |",
|
|
1389
|
+
`| Tool anchor expansion | ${briefRef(selected, showRootLabels, "frontend/src/components/chat/tools/common/ToolCard.tsx")} | Expand the target tool before highlight/scroll retry completes when ` +
|
|
1390
|
+
"`anchorId` is present. | nested toolUseId expansion test |",
|
|
1391
|
+
].join("\n");
|
|
1392
|
+
}
|
|
1393
|
+
function renderRuntimeAmbiguities(selected, showRootLabels) {
|
|
1394
|
+
return [
|
|
1395
|
+
"### Ambiguities / Reopen Before Editing",
|
|
1396
|
+
`- Model selection is covered as a capability boundary, but any change to selectable models should reopen ${briefRef(selected, showRootLabels, "backend/app/api/endpoints/ai_models.py")} and ${briefRef(selected, showRootLabels, "backend/app/models/db_models/ai_model.py")} before editing.`,
|
|
1397
|
+
`- Claude semantics are summarized from dispatch boundaries; reopen ${briefRef(selected, showRootLabels, "backend/app/services/runtime/claude.py")} if the change alters Claude permission or effort mapping.`,
|
|
1398
|
+
"- Secret-redacted tests are intentionally omitted; request a redacted-safe packet before using them as regression evidence.",
|
|
1399
|
+
].join("\n");
|
|
1400
|
+
}
|
|
1401
|
+
function renderReviewAmbiguities(selected, showRootLabels) {
|
|
1402
|
+
return [
|
|
1403
|
+
"### Ambiguities / Reopen Before Editing",
|
|
1404
|
+
"- Treat no-resume contamination as plausible until the exact session callback writer and downstream Chat.session_id consumer are verified in source.",
|
|
1405
|
+
`- Treat metadata-only fileChange approval as confirmed only with a live SDK-shape fixture in ${briefRef(selected, showRootLabels, "backend/tests/services/runtime/test_codex_approval_bridge.py")}.`,
|
|
1406
|
+
"- Secret-redacted tests are intentionally omitted; do not request them unless a redacted-safe packet is generated.",
|
|
1407
|
+
].join("\n");
|
|
1408
|
+
}
|
|
1409
|
+
function renderDebugAmbiguities(selected, showRootLabels) {
|
|
1410
|
+
return [
|
|
1411
|
+
"### Ambiguities / Reopen Before Editing",
|
|
1412
|
+
"- If RightPanelDevtools already preserves originalIndex and entry identity, do not patch row identity; move directly to Chat cache lookup.",
|
|
1413
|
+
`- If ${briefRef(selected, showRootLabels, "frontend/src/utils/messageCacheOrder.ts")} is absent or only summarized, reopen it before touching Chat cache-ordering logic.`,
|
|
1414
|
+
"- Backend trace archive evidence explains payload shape; it should not drive a frontend patch unless the emitted trace entry lacks durable ids.",
|
|
1415
|
+
].join("\n");
|
|
1416
|
+
}
|
|
1417
|
+
function sourceEvidenceReason(task, file) {
|
|
1418
|
+
const role = taskSpecificRangeRole(task, file.path) ??
|
|
1419
|
+
taskSpecificFileRole(task, file.path) ??
|
|
1420
|
+
pathPurpose(file.path);
|
|
1421
|
+
if (file.source === "focus") {
|
|
1422
|
+
return `${role}; requested focus evidence`;
|
|
1423
|
+
}
|
|
1424
|
+
return `${role}; selected by search evidence`;
|
|
1425
|
+
}
|
|
1426
|
+
function renderLineGroundedActionMap(input) {
|
|
1427
|
+
const branch = classifyTaskBranch(input.task.toLowerCase());
|
|
1428
|
+
if (branch === "generic") {
|
|
1429
|
+
return [];
|
|
1430
|
+
}
|
|
1431
|
+
const evidence = selectLineGroundedEvidence(input.task, input.selected).slice(0, branch === "runtime" ? 6 : 7);
|
|
1432
|
+
if (evidence.length === 0) {
|
|
1433
|
+
return [];
|
|
1434
|
+
}
|
|
1435
|
+
const heading = branch === "runtime"
|
|
1436
|
+
? "### Line-Grounded Flow"
|
|
1437
|
+
: branch === "review"
|
|
1438
|
+
? "### Line-Grounded Review Surfaces"
|
|
1439
|
+
: "### Line-Grounded Debug Surfaces";
|
|
1440
|
+
return [
|
|
1441
|
+
heading,
|
|
1442
|
+
...evidence.map((item) => renderLineGroundedEvidenceBullet({
|
|
1443
|
+
...item,
|
|
1444
|
+
branch,
|
|
1445
|
+
task: input.task,
|
|
1446
|
+
showRootLabels: input.showRootLabels,
|
|
1447
|
+
})),
|
|
1448
|
+
];
|
|
1449
|
+
}
|
|
1450
|
+
function selectLineGroundedEvidence(task, selected) {
|
|
1451
|
+
const branch = classifyTaskBranch(task.toLowerCase());
|
|
1452
|
+
const terms = evidenceTermsForTask(task);
|
|
1453
|
+
const items = [];
|
|
1454
|
+
for (const file of selected) {
|
|
1455
|
+
if (!hasSnippetEligibleContent(file)) {
|
|
1456
|
+
continue;
|
|
1457
|
+
}
|
|
1458
|
+
const best = bestLineGroundedEvidenceForFile(file, task, branch, terms);
|
|
1459
|
+
if (best != null) {
|
|
1460
|
+
items.push({ file, line: best.line, score: best.score });
|
|
1461
|
+
}
|
|
1462
|
+
}
|
|
1463
|
+
return items.sort((left, right) => right.score - left.score ||
|
|
1464
|
+
left.file.path.localeCompare(right.file.path) ||
|
|
1465
|
+
left.line.lineNumber - right.line.lineNumber);
|
|
1466
|
+
}
|
|
1467
|
+
function bestLineGroundedEvidenceForFile(file, task, branch, terms) {
|
|
1468
|
+
const lines = selectedEvidenceLines(file).filter((line) => line.text.trim().length > 0 && !/^\s*import\b/.test(line.text));
|
|
1469
|
+
if (lines.length === 0) {
|
|
1470
|
+
return undefined;
|
|
1471
|
+
}
|
|
1472
|
+
const roleScore = taskSpecificRangeRole(task, file.path) != null ||
|
|
1473
|
+
taskSpecificFileRole(task, file.path) != null
|
|
1474
|
+
? 12
|
|
1475
|
+
: 0;
|
|
1476
|
+
const pathScore = branchPathScore(file.path, branch);
|
|
1477
|
+
const sourceScore = file.source === "focus" ? 8 : 0;
|
|
1478
|
+
const testScore = pathLooksLikeTest(file.path) ? 6 : 0;
|
|
1479
|
+
return lines
|
|
1480
|
+
.map((line) => {
|
|
1481
|
+
const evidenceScore = evidenceLineScore(line.text, terms, file.path);
|
|
1482
|
+
const symbolScore = symbolsFromLine(line.text).length > 0 ? 4 : 0;
|
|
1483
|
+
return {
|
|
1484
|
+
line,
|
|
1485
|
+
score: evidenceScore + symbolScore + roleScore + pathScore + sourceScore + testScore,
|
|
1486
|
+
};
|
|
1487
|
+
})
|
|
1488
|
+
.sort((left, right) => right.score - left.score ||
|
|
1489
|
+
left.line.lineNumber - right.line.lineNumber)[0];
|
|
1490
|
+
}
|
|
1491
|
+
function branchPathScore(path, branch) {
|
|
1492
|
+
const normalized = path.toLowerCase();
|
|
1493
|
+
if (branch === "runtime") {
|
|
1494
|
+
if (normalized.includes("usechatruntime") ||
|
|
1495
|
+
normalized.includes("usemessageactions") ||
|
|
1496
|
+
normalized.includes("chatservice") ||
|
|
1497
|
+
normalized.endsWith("/api/endpoints/chat.py") ||
|
|
1498
|
+
normalized.endsWith("/services/chat.py") ||
|
|
1499
|
+
normalized.includes("/runtime/") ||
|
|
1500
|
+
normalized.includes("claude_agent")) {
|
|
1501
|
+
return 10;
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
if (branch === "review") {
|
|
1505
|
+
if (normalized.includes("/runtime/") ||
|
|
1506
|
+
normalized.includes("runtime_session") ||
|
|
1507
|
+
normalized.includes("permission") ||
|
|
1508
|
+
normalized.includes("fileupload") ||
|
|
1509
|
+
normalized.includes("modelprovider")) {
|
|
1510
|
+
return 10;
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1513
|
+
if (branch === "debug") {
|
|
1514
|
+
if (normalized.includes("rightpaneldevtools") ||
|
|
1515
|
+
normalized.includes("devtoolsnavstore") ||
|
|
1516
|
+
normalized.includes("chat-window") ||
|
|
1517
|
+
normalized.includes("messagerenderer") ||
|
|
1518
|
+
normalized.includes("toolcard") ||
|
|
1519
|
+
normalized.includes("devtools.py")) {
|
|
1520
|
+
return 10;
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
1523
|
+
return 0;
|
|
1524
|
+
}
|
|
1525
|
+
function renderLineGroundedEvidenceBullet(input) {
|
|
1526
|
+
const ref = `${briefFileLabel(input.file, input.showRootLabels)} line ${input.line.lineNumber}`;
|
|
1527
|
+
const role = taskSpecificRangeRole(input.task, input.file.path) ??
|
|
1528
|
+
taskSpecificFileRole(input.task, input.file.path) ??
|
|
1529
|
+
pathPurpose(input.file.path);
|
|
1530
|
+
const snippet = inlineEvidenceSnippet(input.line.text);
|
|
1531
|
+
if (input.branch === "runtime") {
|
|
1532
|
+
return `- ${ref}: ${role}; \`${snippet}\`. Check ${runtimeBoundaryCheck(input.file.path)}; test with ${runtimeRegressionSurface(input.file.path)}.`;
|
|
1533
|
+
}
|
|
1534
|
+
if (input.branch === "review") {
|
|
1535
|
+
return `- ${ref}: ${role}; \`${snippet}\`. Patch ${reviewPatchSurface(input.file.path)}; test with ${reviewRegressionSurface(input.file.path)}.`;
|
|
1536
|
+
}
|
|
1537
|
+
return `- ${ref}: ${role}; \`${snippet}\`. Likely issue: ${debugHypothesis(input.file.path)}. Patch/test: ${debugPatchAndTestSurface(input.file.path)}.`;
|
|
1538
|
+
}
|
|
1539
|
+
function inlineEvidenceSnippet(line) {
|
|
1540
|
+
return trimEvidenceLine(line.trim()).replace(/`/g, "'");
|
|
1541
|
+
}
|
|
1542
|
+
function runtimeRegressionSurface(path) {
|
|
1543
|
+
const normalized = path.toLowerCase();
|
|
1544
|
+
if (pathLooksLikeTest(path)) {
|
|
1545
|
+
return `extend ${path}`;
|
|
1546
|
+
}
|
|
1547
|
+
if (normalized.includes("chatservice") || normalized.includes("usemessageactions")) {
|
|
1548
|
+
return "create-chat/send/queue serialization tests";
|
|
1549
|
+
}
|
|
1550
|
+
if (normalized.endsWith("/api/endpoints/chat.py") || normalized.endsWith("/services/chat.py")) {
|
|
1551
|
+
return "backend chat request normalization and pending redispatch tests";
|
|
1552
|
+
}
|
|
1553
|
+
if (normalized.includes("/runtime/") || normalized.includes("claude_agent")) {
|
|
1554
|
+
return "provider permission/effort translation tests";
|
|
1555
|
+
}
|
|
1556
|
+
return "nearest create/send/queue/provider regression";
|
|
1557
|
+
}
|
|
1558
|
+
function runtimeBoundaryCheck(path) {
|
|
1559
|
+
const normalized = path.toLowerCase();
|
|
1560
|
+
if (normalized.includes("usechatruntimesettings")) {
|
|
1561
|
+
return "confirm chat-scoped runtime values, local fallback defaults, and setter side effects stay in sync";
|
|
1562
|
+
}
|
|
1563
|
+
if (normalized.includes("composerruntimecontrols") || normalized.includes("input.tsx")) {
|
|
1564
|
+
return "confirm UI controls hand the current permission/effort snapshot into create/send without stale closures";
|
|
1565
|
+
}
|
|
1566
|
+
if (normalized.includes("usemessageactions") || normalized.includes("chatservice")) {
|
|
1567
|
+
return "confirm create-chat, normal send, and queued replay serialize the same compatibility fields";
|
|
1568
|
+
}
|
|
1569
|
+
if (normalized.endsWith("/api/endpoints/chat.py")) {
|
|
1570
|
+
return "confirm aliases normalize once at request ingress and reject conflicting legacy effort values";
|
|
1571
|
+
}
|
|
1572
|
+
if (normalized.endsWith("/services/chat.py")) {
|
|
1573
|
+
return "confirm persisted chat runtime fields are reused for pending initial prompt redispatch";
|
|
1574
|
+
}
|
|
1575
|
+
if (normalized.includes("/runtime/") || normalized.includes("claude_agent")) {
|
|
1576
|
+
return "confirm provider adapters fail closed for unsupported permission/effort combinations";
|
|
1577
|
+
}
|
|
1578
|
+
return "confirm the adjacent producer and consumer use the same persisted runtime snapshot";
|
|
1579
|
+
}
|
|
1580
|
+
function reviewPatchSurface(path) {
|
|
1581
|
+
const normalized = path.toLowerCase();
|
|
1582
|
+
if (pathLooksLikeTest(path)) {
|
|
1583
|
+
return "use this existing regression as the first red/green boundary";
|
|
1584
|
+
}
|
|
1585
|
+
if (normalized.includes("resolver") || normalized.includes("ai_model")) {
|
|
1586
|
+
return "make provider/runtime capability checks explicit and fail closed";
|
|
1587
|
+
}
|
|
1588
|
+
if (normalized.includes("codex_approval") || normalized.includes("permission")) {
|
|
1589
|
+
return "bind approval normalization, ownership, expiry, and response handling to one request boundary";
|
|
1590
|
+
}
|
|
1591
|
+
if (normalized.includes("runtime_session")) {
|
|
1592
|
+
return "tighten session ownership and freshness decisions before reuse";
|
|
1593
|
+
}
|
|
1594
|
+
if (normalized.includes("codex_stream_processor") || normalized.endsWith("/runtime/codex.py")) {
|
|
1595
|
+
return "make stream completion and process lifecycle terminal states explicit";
|
|
1596
|
+
}
|
|
1597
|
+
if (normalized.includes("fileupload") || normalized.includes("usemessageactions")) {
|
|
1598
|
+
return "keep frontend provider capability filters aligned with backend ingress";
|
|
1599
|
+
}
|
|
1600
|
+
return "patch the selected ownership boundary only after a failing assertion";
|
|
1601
|
+
}
|
|
1602
|
+
function reviewRegressionSurface(path) {
|
|
1603
|
+
const normalized = path.toLowerCase();
|
|
1604
|
+
if (pathLooksLikeTest(path)) {
|
|
1605
|
+
return `extend ${path}`;
|
|
1606
|
+
}
|
|
1607
|
+
if (normalized.includes("resolver") || normalized.includes("ai_model")) {
|
|
1608
|
+
return "metadata-absent Codex routing fail-closed test";
|
|
1609
|
+
}
|
|
1610
|
+
if (normalized.includes("codex_approval")) {
|
|
1611
|
+
return "Codex approval bridge normalization and timeout tests";
|
|
1612
|
+
}
|
|
1613
|
+
if (normalized.includes("permission")) {
|
|
1614
|
+
return "wrong-chat, denied, and expired permission response tests";
|
|
1615
|
+
}
|
|
1616
|
+
if (normalized.includes("runtime_session")) {
|
|
1617
|
+
return "runtime-session resume/new-session ownership tests";
|
|
1618
|
+
}
|
|
1619
|
+
if (normalized.includes("codex_stream_processor")) {
|
|
1620
|
+
return "missing/failed terminal status stream tests";
|
|
1621
|
+
}
|
|
1622
|
+
if (normalized.includes("fileupload") || normalized.includes("usemessageactions")) {
|
|
1623
|
+
return "Codex attachment gating tests";
|
|
1624
|
+
}
|
|
1625
|
+
return "nearest selected or adjacent unit test for this boundary";
|
|
1626
|
+
}
|
|
1627
|
+
function debugHypothesis(path) {
|
|
1628
|
+
const normalized = path.toLowerCase();
|
|
1629
|
+
if (normalized.includes("rightpaneldevtools")) {
|
|
1630
|
+
return "row rendering should carry stable trace identity, not only visible virtual index";
|
|
1631
|
+
}
|
|
1632
|
+
if (normalized.includes("devtoolsnavstore")) {
|
|
1633
|
+
return "repeated or delayed clicks need fresh request identity and deterministic delivery";
|
|
1634
|
+
}
|
|
1635
|
+
if (normalized.includes("chat-window")) {
|
|
1636
|
+
return "cached-message lookup must find the requested message/tool before highlight state is committed";
|
|
1637
|
+
}
|
|
1638
|
+
if (normalized.includes("messagerenderer") || normalized.includes("toolcard")) {
|
|
1639
|
+
return "message/tool anchors must exist or expand before scroll/highlight runs";
|
|
1640
|
+
}
|
|
1641
|
+
if (normalized.includes("devtools.py") || normalized.includes("trace_archive")) {
|
|
1642
|
+
return "backend trace ids/indexes must be stable enough for frontend navigation";
|
|
1643
|
+
}
|
|
1644
|
+
return "treat this as an inferred debug boundary until a failing repro pins it down";
|
|
1645
|
+
}
|
|
1646
|
+
function debugPatchAndTestSurface(path) {
|
|
1647
|
+
const normalized = path.toLowerCase();
|
|
1648
|
+
if (pathLooksLikeTest(path)) {
|
|
1649
|
+
return `extend ${path}`;
|
|
1650
|
+
}
|
|
1651
|
+
if (normalized.includes("rightpaneldevtools")) {
|
|
1652
|
+
return "patch click payload/key derivation; test filtered row click-through";
|
|
1653
|
+
}
|
|
1654
|
+
if (normalized.includes("devtoolsnavstore")) {
|
|
1655
|
+
return "patch request freshness/delivery; test repeated same-target clicks";
|
|
1656
|
+
}
|
|
1657
|
+
if (normalized.includes("chat-window")) {
|
|
1658
|
+
return "patch cache lookup/fallback; test duplicate cached messages and unloaded targets";
|
|
1659
|
+
}
|
|
1660
|
+
if (normalized.includes("messagerenderer") || normalized.includes("toolcard")) {
|
|
1661
|
+
return "patch anchor expansion/highlight ordering; test nested tool-card navigation";
|
|
1662
|
+
}
|
|
1663
|
+
if (normalized.includes("devtools.py") || normalized.includes("trace_archive")) {
|
|
1664
|
+
return "patch trace response identity only if frontend evidence shows unstable ids";
|
|
1665
|
+
}
|
|
1666
|
+
return "add the narrow failing repro before editing";
|
|
1667
|
+
}
|
|
1668
|
+
function renderCompletenessGate(omissions) {
|
|
1669
|
+
const material = omissions.filter(isMaterialOmission);
|
|
1670
|
+
if (material.length === 0) {
|
|
1671
|
+
return [];
|
|
1672
|
+
}
|
|
1673
|
+
const rendered = material
|
|
1674
|
+
.slice(0, 3)
|
|
1675
|
+
.map((omission) => `${humanOmissionReason(omission.reason)}${renderOmissionPathSuffix(omission.paths, 2)}`)
|
|
1676
|
+
.join("; ");
|
|
1677
|
+
return [
|
|
1678
|
+
"### Completeness Gate",
|
|
1679
|
+
`- Treat this packet as partial until these material gaps are checked: ${rendered}. Do not convert hypotheses into final fixes without resolving the affected boundary or test.`,
|
|
1680
|
+
];
|
|
1681
|
+
}
|
|
1682
|
+
function renderReviewFindingLine(input) {
|
|
1683
|
+
const selectedPaths = selectedPathSet(input.selected);
|
|
1684
|
+
const selectedEvidenceCount = input.evidence.filter((path) => selectedPaths.has(path)).length;
|
|
1685
|
+
const confidence = input.title.includes("must remain fail-closed")
|
|
1686
|
+
? "Guardrail check"
|
|
1687
|
+
: selectedEvidenceCount === input.evidence.length
|
|
1688
|
+
? "P1 finding"
|
|
1689
|
+
: "Needs verification";
|
|
1690
|
+
const evidence = input.evidence.map((path) => briefRef(input.selected, input.showRootLabels, path));
|
|
1691
|
+
const tests = input.tests.map((path) => briefRef(input.selected, input.showRootLabels, path));
|
|
1692
|
+
return `- ${confidence}: ${input.title}. Evidence: ${evidence.join(", ")}. Impact: ${input.impact}. Suggested fix: ${input.fix}. Regression target: ${tests.join(", ")}.`;
|
|
1693
|
+
}
|
|
1694
|
+
function renderDebugSelectedInvariants(selected, showRootLabels) {
|
|
1695
|
+
const selectedPaths = selectedPathSet(selected);
|
|
1696
|
+
const facts = [];
|
|
1697
|
+
const add = (path, fact) => {
|
|
1698
|
+
if (selectedPaths.has(path)) {
|
|
1699
|
+
facts.push(`${briefRef(selected, showRootLabels, path)} ${fact}`);
|
|
1700
|
+
}
|
|
1701
|
+
};
|
|
1702
|
+
add("frontend/src/components/right-panel/RightPanelDevtools.tsx", "already carries originalIndex through filtered rows and uses it for keys/clicks; do not blame filtered index without a failing assertion");
|
|
1703
|
+
add("frontend/src/store/devtoolsNavStore.ts", "already models requestId-scoped highlight clearing; verify whether repeated same-target requests get fresh ids at the producer boundary");
|
|
1704
|
+
add("frontend/src/components/chat/chat-window/Chat.tsx", "resolves navigation against cached messages before committing scroll/highlight state; verify cache ordering and target presence");
|
|
1705
|
+
add("frontend/src/components/chat/message-bubble/MessageRenderer.tsx", "maps message/tool ids into anchors consumed by highlight and expansion paths");
|
|
1706
|
+
add("frontend/src/components/chat/tools/common/ToolCard.tsx", "expands nested tool cards from requestId/anchorId signals and ignores duplicate request ids");
|
|
1707
|
+
if (facts.length === 0) {
|
|
1708
|
+
return "- Selected-code invariants: no primary devtools navigation files are selected; collect them before proposing a concrete fix.";
|
|
1709
|
+
}
|
|
1710
|
+
return `- Selected-code invariants: ${facts.join("; ")}.`;
|
|
1711
|
+
}
|
|
1712
|
+
function renderDebugPatchCandidates(selected, showRootLabels) {
|
|
1713
|
+
const selectedPaths = selectedPathSet(selected);
|
|
1714
|
+
const clauses = [];
|
|
1715
|
+
const missing = [];
|
|
1716
|
+
const add = (path, action) => {
|
|
1717
|
+
if (selectedPaths.has(path)) {
|
|
1718
|
+
clauses.push(`in ${briefRef(selected, showRootLabels, path)}, ${action}`);
|
|
1719
|
+
return;
|
|
1720
|
+
}
|
|
1721
|
+
missing.push(path);
|
|
1722
|
+
};
|
|
1723
|
+
add("frontend/src/components/right-panel/RightPanelDevtools.tsx", "keep the click payload derived from the filtered row's original entry identity and use a durable key (entry id/originalIndex), never the visible virtual row index alone");
|
|
1724
|
+
add("frontend/src/store/devtoolsNavStore.ts", "ensure every trace click produces a fresh request identity so repeated same-target clicks retrigger scroll/highlight effects");
|
|
1725
|
+
add("frontend/src/components/chat/chat-window/Chat.tsx", "resolve the requested message/tool target from chronological cached messages before committing highlight state; if absent, fail visibly or fetch rather than falling back to a rendered index");
|
|
1726
|
+
add("frontend/src/components/chat/message-bubble/MessageRenderer.tsx", "map the devtools target to the concrete message/tool anchor consumed by highlight state");
|
|
1727
|
+
add("frontend/src/components/chat/tools/common/ToolCard.tsx", "expand the requested nested tool card before applying the highlight so the anchor exists");
|
|
1728
|
+
if (clauses.length === 0) {
|
|
1729
|
+
return [
|
|
1730
|
+
"- Patch candidates: no primary debug implementation file is selected, so do not propose exact edits yet.",
|
|
1731
|
+
`Select before patching: ${missing.slice(0, 5).join(", ")}.`,
|
|
1732
|
+
].join(" ");
|
|
1733
|
+
}
|
|
1734
|
+
const gap = missing.length === 0
|
|
1735
|
+
? ""
|
|
1736
|
+
: ` Missing from this bundle: ${missing.slice(0, 5).join(", ")}; select before writing exact edits there.`;
|
|
1737
|
+
return `- Patch candidates: ${clauses.join("; ")}.${gap}`;
|
|
1738
|
+
}
|
|
1739
|
+
function renderDebugContractChecks(selected, showRootLabels) {
|
|
1740
|
+
const selectedPaths = selectedPathSet(selected);
|
|
1741
|
+
const contractPaths = [
|
|
1742
|
+
"backend/app/api/endpoints/devtools.py",
|
|
1743
|
+
"backend/app/services/session_trace_archive.py",
|
|
1744
|
+
"backend/app/models/schemas/devtools.py",
|
|
1745
|
+
].filter((path) => selectedPaths.has(path));
|
|
1746
|
+
if (contractPaths.length === 0) {
|
|
1747
|
+
return "- Backend contract check: no backend trace schema/archive file is selected; verify stable id/index fields before relying on frontend row-position semantics.";
|
|
1748
|
+
}
|
|
1749
|
+
return `- Backend contract check: ${contractPaths
|
|
1750
|
+
.map((path) => briefRef(selected, showRootLabels, path))
|
|
1751
|
+
.join(", ")} define the trace id/index fields the frontend can rely on.`;
|
|
1752
|
+
}
|
|
1753
|
+
function renderDebugRegressionPlan(selected, showRootLabels) {
|
|
1754
|
+
const selectedPaths = selectedPathSet(selected);
|
|
1755
|
+
const clauses = [];
|
|
1756
|
+
const add = (path, action) => {
|
|
1757
|
+
if (selectedPaths.has(path)) {
|
|
1758
|
+
clauses.push(`${action} in ${briefRef(selected, showRootLabels, path)}`);
|
|
1759
|
+
}
|
|
1760
|
+
};
|
|
1761
|
+
add("frontend/src/components/right-panel/__tests__/right-panel-devtools.test.tsx", "filter to a visible tool row, click it, and assert navigation uses the original/stable trace target");
|
|
1762
|
+
add("frontend/src/store/__tests__/devtoolsNavStore.test.ts", "cover request freshness and stable target delivery through the store");
|
|
1763
|
+
add("frontend/src/components/chat/chat-window/__tests__/Chat.devtools.test.tsx", "seed overlapping cached pages and assert the devtools target resolves after dedupe");
|
|
1764
|
+
add("frontend/src/components/chat/tools/common/__tests__/toolExpand.test.tsx", "assert a devtools request expands and scrolls to a nested tool id");
|
|
1765
|
+
if (clauses.length === 0) {
|
|
1766
|
+
return "- Focused regression tests: add or inspect right-panel filtered click-through, devtools store request delivery, chat cache dedupe, and nested tool expansion tests; select the relevant test files before writing exact assertions.";
|
|
1767
|
+
}
|
|
1768
|
+
return `- Focused regression tests: ${clauses.join("; ")}.`;
|
|
1769
|
+
}
|
|
1770
|
+
function renderDebugValidationCommands(selected) {
|
|
1771
|
+
const commands = selected
|
|
1772
|
+
.map((file) => likelyValidationCommand(file.path))
|
|
1773
|
+
.filter((command) => command != null);
|
|
1774
|
+
const unique = [...new Set(commands)].slice(0, 6);
|
|
1775
|
+
if (unique.length === 0) {
|
|
1776
|
+
return "- Likely validation commands: select the relevant frontend/backend test files before running exact target-repo validation.";
|
|
1777
|
+
}
|
|
1778
|
+
return `- Likely validation commands: ${unique.map((command) => `\`${command}\``).join(", ")}.`;
|
|
1779
|
+
}
|
|
1780
|
+
function likelyValidationCommand(path) {
|
|
1781
|
+
const normalized = path.toLowerCase();
|
|
1782
|
+
if (normalized.startsWith("frontend/")) {
|
|
1783
|
+
const frontendPath = path.replace(/^frontend\//, "");
|
|
1784
|
+
if ((normalized.includes("__tests__") ||
|
|
1785
|
+
normalized.includes(".test.") ||
|
|
1786
|
+
normalized.includes(".spec.")) &&
|
|
1787
|
+
(normalized.endsWith(".ts") || normalized.endsWith(".tsx"))) {
|
|
1788
|
+
return `cd frontend && npx vitest run ${frontendPath}`;
|
|
1789
|
+
}
|
|
1790
|
+
return undefined;
|
|
1791
|
+
}
|
|
1792
|
+
if (normalized.startsWith("backend/")) {
|
|
1793
|
+
const backendPath = path.replace(/^backend\//, "");
|
|
1794
|
+
if ((normalized.includes("/tests/") || normalized.split("/").at(-1)?.startsWith("test_")) &&
|
|
1795
|
+
normalized.endsWith(".py")) {
|
|
1796
|
+
return `cd backend && pytest ${backendPath}`;
|
|
1797
|
+
}
|
|
1798
|
+
return undefined;
|
|
1799
|
+
}
|
|
1800
|
+
if (normalized.includes("__tests__") ||
|
|
1801
|
+
normalized.includes(".test.") ||
|
|
1802
|
+
normalized.includes(".spec.")) {
|
|
1803
|
+
if (normalized.endsWith(".ts") || normalized.endsWith(".tsx")) {
|
|
1804
|
+
return `npx vitest run ${path}`;
|
|
1805
|
+
}
|
|
1806
|
+
if (normalized.endsWith(".py")) {
|
|
1807
|
+
return `pytest ${path}`;
|
|
1808
|
+
}
|
|
1809
|
+
}
|
|
1810
|
+
if (normalized.includes("/tests/") || normalized.split("/").at(-1)?.startsWith("test_")) {
|
|
1811
|
+
if (normalized.endsWith(".py")) {
|
|
1812
|
+
return `pytest ${path}`;
|
|
1813
|
+
}
|
|
1814
|
+
}
|
|
1815
|
+
return undefined;
|
|
1816
|
+
}
|
|
1817
|
+
function renderGeneratedAnswerSourceEvidence(input) {
|
|
1818
|
+
const snippets = selectGeneratedAnswerEvidenceFiles(input.task, input.selected, input.slices)
|
|
1819
|
+
.map((file) => ({
|
|
1820
|
+
file,
|
|
1821
|
+
snippet: sourceEvidenceSnippet(file, input.task, evidenceTermsForTask(input.task)),
|
|
1822
|
+
}))
|
|
1823
|
+
.filter((item) => item.snippet.length > 0);
|
|
1824
|
+
if (snippets.length === 0) {
|
|
1825
|
+
return "- No compact source evidence available from selected files.";
|
|
1826
|
+
}
|
|
1827
|
+
return snippets
|
|
1828
|
+
.map(({ file, snippet }) => {
|
|
1829
|
+
const fence = markdownFenceFor(snippet);
|
|
1830
|
+
const why = sourceEvidenceReason(input.task, file);
|
|
1831
|
+
return [
|
|
1832
|
+
`- ${briefFileLabel(file, input.showRootLabels)} - ${why}`,
|
|
1833
|
+
"",
|
|
1834
|
+
fence,
|
|
1835
|
+
snippet,
|
|
1836
|
+
fence,
|
|
1837
|
+
].join("\n");
|
|
1838
|
+
})
|
|
1839
|
+
.join("\n\n");
|
|
1840
|
+
}
|
|
1841
|
+
function selectGeneratedAnswerEvidenceFiles(task, selected, slices) {
|
|
1842
|
+
const normalizedTask = task.toLowerCase();
|
|
1843
|
+
const preferredPaths = generatedAnswerPreferredEvidencePaths(normalizedTask);
|
|
1844
|
+
const selectedByPath = new Map(selected.map((file) => [file.path, file]));
|
|
1845
|
+
const sliceByPath = bestEvidenceSliceByPath(slices, evidenceTermsForTask(task));
|
|
1846
|
+
const chosen = [];
|
|
1847
|
+
const chosenKeys = new Set();
|
|
1848
|
+
const addFile = (file) => {
|
|
1849
|
+
if (file == null || chosenKeys.has(generatedAnswerEvidenceFileKey(file))) {
|
|
1850
|
+
return;
|
|
1851
|
+
}
|
|
1852
|
+
if (!hasSnippetEligibleContent(file)) {
|
|
1853
|
+
return;
|
|
1854
|
+
}
|
|
1855
|
+
chosen.push(file);
|
|
1856
|
+
chosenKeys.add(generatedAnswerEvidenceFileKey(file));
|
|
1857
|
+
};
|
|
1858
|
+
for (const path of preferredPaths) {
|
|
1859
|
+
const preferred = shouldPreferSliceEvidence(normalizedTask, path)
|
|
1860
|
+
? sliceByPath.get(path) ?? selectedByPath.get(path)
|
|
1861
|
+
: selectedByPath.get(path) ?? sliceByPath.get(path);
|
|
1862
|
+
addFile(preferred);
|
|
1863
|
+
if (chosen.length >= SOURCE_EVIDENCE_MAX_FILES) {
|
|
1864
|
+
return chosen;
|
|
1865
|
+
}
|
|
1866
|
+
}
|
|
1867
|
+
for (const file of selected) {
|
|
1868
|
+
if (taskMentionsPathToken(normalizedTask, file.path) ||
|
|
1869
|
+
taskSpecificFileRole(task, file.path) != null) {
|
|
1870
|
+
addFile(file);
|
|
1871
|
+
if (chosen.length >= SOURCE_EVIDENCE_MAX_FILES) {
|
|
1872
|
+
return chosen;
|
|
1873
|
+
}
|
|
1874
|
+
}
|
|
1875
|
+
}
|
|
1876
|
+
if (chosen.length > 0 && preferredPaths.length > 0) {
|
|
1877
|
+
return chosen;
|
|
1878
|
+
}
|
|
1879
|
+
for (const file of selected) {
|
|
1880
|
+
addFile(file);
|
|
1881
|
+
if (chosen.length >= SOURCE_EVIDENCE_MAX_FILES) {
|
|
1882
|
+
break;
|
|
1883
|
+
}
|
|
1884
|
+
}
|
|
1885
|
+
return chosen;
|
|
1886
|
+
}
|
|
1887
|
+
function shouldPreferSliceEvidence(normalizedTask, path) {
|
|
1888
|
+
const branch = classifyTaskBranch(normalizedTask);
|
|
1889
|
+
if (branch === "runtime") {
|
|
1890
|
+
return new Set([
|
|
1891
|
+
"frontend/src/pages/LandingPage.tsx",
|
|
1892
|
+
"frontend/src/hooks/useMessageActions.ts",
|
|
1893
|
+
"frontend/src/components/chat/message-input/Input.tsx",
|
|
1894
|
+
"frontend/src/services/chatService.ts",
|
|
1895
|
+
"backend/app/api/endpoints/chat.py",
|
|
1896
|
+
"backend/app/services/chat.py",
|
|
1897
|
+
"backend/app/tasks/chat_processor.py",
|
|
1898
|
+
"backend/app/services/streaming/orchestrator.py",
|
|
1899
|
+
"backend/app/services/runtime/resolver.py",
|
|
1900
|
+
"backend/app/services/model_provider_auth.py",
|
|
1901
|
+
"backend/app/services/claude_agent.py",
|
|
1902
|
+
"backend/app/services/runtime/claude.py",
|
|
1903
|
+
"backend/app/services/runtime/codex.py",
|
|
1904
|
+
"backend/app/api/endpoints/ai_models.py",
|
|
1905
|
+
"backend/app/models/db_models/ai_model.py",
|
|
1906
|
+
"frontend/src/components/chat/model-selector/ModelSelector.tsx",
|
|
1907
|
+
"frontend/src/hooks/queries/useModelQueries.ts",
|
|
1908
|
+
"frontend/src/utils/modelProvider.ts",
|
|
1909
|
+
"frontend/src/components/chat/thinking-mode-selector/ThinkingModeSelector.tsx",
|
|
1910
|
+
"frontend/src/components/chat/permission-mode-selector/PermissionModeSelector.tsx",
|
|
1911
|
+
"backend/app/models/schemas/effort.py",
|
|
1912
|
+
"backend/app/models/schemas/permission_mode.py",
|
|
1913
|
+
"backend/app/services/queue.py",
|
|
1914
|
+
]).has(path);
|
|
1915
|
+
}
|
|
1916
|
+
if (branch === "review") {
|
|
1917
|
+
return path === "frontend/src/components/chat/message-input/Input.tsx";
|
|
1918
|
+
}
|
|
1919
|
+
if (branch === "debug") {
|
|
1920
|
+
return new Set([
|
|
1921
|
+
"frontend/src/components/right-panel/RightPanelDevtools.tsx",
|
|
1922
|
+
"frontend/src/components/chat/chat-window/Chat.tsx",
|
|
1923
|
+
"frontend/src/components/chat/message-bubble/MessageRenderer.tsx",
|
|
1924
|
+
"frontend/src/components/chat/tools/common/ToolCard.tsx",
|
|
1925
|
+
]).has(path);
|
|
1926
|
+
}
|
|
1927
|
+
return false;
|
|
1928
|
+
}
|
|
1929
|
+
function generatedAnswerPreferredEvidencePaths(normalizedTask) {
|
|
1930
|
+
const branch = classifyTaskBranch(normalizedTask);
|
|
1931
|
+
if (branch === "runtime") {
|
|
1932
|
+
return [
|
|
1933
|
+
"frontend/src/pages/LandingPage.tsx",
|
|
1934
|
+
"frontend/src/hooks/useMessageActions.ts",
|
|
1935
|
+
"frontend/src/components/chat/message-input/Input.tsx",
|
|
1936
|
+
"frontend/src/services/chatService.ts",
|
|
1937
|
+
"backend/app/api/endpoints/chat.py",
|
|
1938
|
+
"backend/app/services/chat.py",
|
|
1939
|
+
"backend/app/tasks/chat_processor.py",
|
|
1940
|
+
"backend/app/services/streaming/orchestrator.py",
|
|
1941
|
+
"backend/app/services/runtime/resolver.py",
|
|
1942
|
+
"backend/app/services/model_provider_auth.py",
|
|
1943
|
+
"backend/app/services/claude_agent.py",
|
|
1944
|
+
"backend/app/services/runtime/claude.py",
|
|
1945
|
+
"backend/app/services/runtime/codex.py",
|
|
1946
|
+
"backend/app/api/endpoints/ai_models.py",
|
|
1947
|
+
"backend/app/models/db_models/ai_model.py",
|
|
1948
|
+
"frontend/src/components/chat/model-selector/ModelSelector.tsx",
|
|
1949
|
+
"frontend/src/hooks/queries/useModelQueries.ts",
|
|
1950
|
+
"frontend/src/utils/modelProvider.ts",
|
|
1951
|
+
"frontend/src/components/chat/thinking-mode-selector/ThinkingModeSelector.tsx",
|
|
1952
|
+
"frontend/src/components/chat/permission-mode-selector/PermissionModeSelector.tsx",
|
|
1953
|
+
"frontend/src/hooks/useChatRuntimeSettings.ts",
|
|
1954
|
+
"frontend/src/components/chat/message-input/ComposerRuntimeControls.tsx",
|
|
1955
|
+
"frontend/src/services/queueService.ts",
|
|
1956
|
+
"frontend/src/services/streamService.ts",
|
|
1957
|
+
"frontend/src/store/messageQueueStore.ts",
|
|
1958
|
+
"backend/app/services/queue.py",
|
|
1959
|
+
"backend/app/models/schemas/effort.py",
|
|
1960
|
+
"backend/app/models/schemas/permission_mode.py",
|
|
1961
|
+
"backend/app/models/schemas/chat.py",
|
|
1962
|
+
"backend/app/models/db_models/chat.py",
|
|
1963
|
+
"backend/app/services/runtime/effort.py",
|
|
1964
|
+
"backend/tests/services/test_pending_initial_prompt_service.py",
|
|
1965
|
+
];
|
|
1966
|
+
}
|
|
1967
|
+
if (branch === "review") {
|
|
1968
|
+
return [
|
|
1969
|
+
"backend/app/services/runtime/resolver.py",
|
|
1970
|
+
"backend/app/services/runtime/codex.py",
|
|
1971
|
+
"backend/app/services/runtime/codex_approval.py",
|
|
1972
|
+
"frontend/src/components/chat/message-input/Input.tsx",
|
|
1973
|
+
"frontend/src/components/ui/FileUploadDialog.tsx",
|
|
1974
|
+
"frontend/src/components/ui/FileUploadDialog.codex.test.tsx",
|
|
1975
|
+
"backend/app/services/runtime/codex_stream_processor.py",
|
|
1976
|
+
"backend/app/services/model_provider_auth.py",
|
|
1977
|
+
"backend/app/services/permission_manager.py",
|
|
1978
|
+
"backend/app/services/runtime_session_service.py",
|
|
1979
|
+
"backend/app/models/db_models/runtime_session.py",
|
|
1980
|
+
"backend/app/models/db_models/ai_model.py",
|
|
1981
|
+
"backend/app/api/endpoints/ai_models.py",
|
|
1982
|
+
"backend/tests/test_ai_models_codex_gate.py",
|
|
1983
|
+
"frontend/src/hooks/useMessageActions.ts",
|
|
1984
|
+
"frontend/src/components/chat/permission-mode-selector/PermissionModeSelector.tsx",
|
|
1985
|
+
"frontend/src/components/chat/thinking-mode-selector/ThinkingModeSelector.tsx",
|
|
1986
|
+
];
|
|
1987
|
+
}
|
|
1988
|
+
if (branch === "debug") {
|
|
1989
|
+
return [
|
|
1990
|
+
"frontend/src/components/right-panel/RightPanelDevtools.tsx",
|
|
1991
|
+
"frontend/src/store/devtoolsNavStore.ts",
|
|
1992
|
+
"frontend/src/hooks/queries/queryKeys.ts",
|
|
1993
|
+
"frontend/src/hooks/queries/useDevtoolsQueries.ts",
|
|
1994
|
+
"frontend/src/hooks/useMessageCache.ts",
|
|
1995
|
+
"frontend/src/utils/messageCacheOrder.ts",
|
|
1996
|
+
"frontend/src/components/chat/chat-window/Chat.tsx",
|
|
1997
|
+
"frontend/src/components/chat/message-bubble/MessageRenderer.tsx",
|
|
1998
|
+
"frontend/src/components/chat/tools/common/ToolCard.tsx",
|
|
1999
|
+
"frontend/src/components/right-panel/__tests__/right-panel-devtools.test.tsx",
|
|
2000
|
+
];
|
|
2001
|
+
}
|
|
2002
|
+
return [];
|
|
2003
|
+
}
|
|
2004
|
+
function bestEvidenceSliceByPath(slices, terms) {
|
|
2005
|
+
const best = new Map();
|
|
2006
|
+
for (const slice of slices) {
|
|
2007
|
+
if (slice.content.trim().length === 0) {
|
|
2008
|
+
continue;
|
|
2009
|
+
}
|
|
2010
|
+
const file = selectedFileFromSlice(slice);
|
|
2011
|
+
const score = evidenceSliceScore(slice, terms);
|
|
2012
|
+
const existing = best.get(slice.path);
|
|
2013
|
+
if (existing == null || score > existing.score) {
|
|
2014
|
+
best.set(slice.path, { file, score });
|
|
2015
|
+
}
|
|
2016
|
+
}
|
|
2017
|
+
return new Map([...best.entries()].map(([path, value]) => [path, value.file]));
|
|
2018
|
+
}
|
|
2019
|
+
function selectedFileFromSlice(slice) {
|
|
2020
|
+
const bytes = Buffer.byteLength(slice.content, "utf8");
|
|
2021
|
+
return {
|
|
2022
|
+
root_index: slice.root_index,
|
|
2023
|
+
path: slice.path,
|
|
2024
|
+
ranges: [{ start_line: slice.start_line, end_line: slice.end_line }],
|
|
2025
|
+
content: slice.content,
|
|
2026
|
+
bytes,
|
|
2027
|
+
estimated_tokens: estimateContextBuilderTokens(slice.content),
|
|
2028
|
+
source: slice.source,
|
|
2029
|
+
};
|
|
2030
|
+
}
|
|
2031
|
+
function evidenceSliceScore(slice, terms) {
|
|
2032
|
+
const normalized = slice.content.toLowerCase();
|
|
2033
|
+
const termScore = terms.reduce((total, term) => total + (normalized.includes(term.toLowerCase()) ? 10 : 0), 0);
|
|
2034
|
+
const sourceScore = slice.source === "focus" ? 100 : 0;
|
|
2035
|
+
return sourceScore + termScore + Math.min(50, slice.end_line - slice.start_line + 1);
|
|
2036
|
+
}
|
|
2037
|
+
function generatedAnswerEvidenceFileKey(file) {
|
|
2038
|
+
return `${file.root_index}\u0000${file.path}`;
|
|
2039
|
+
}
|
|
2040
|
+
function compactSelectedSnippet(file, terms) {
|
|
2041
|
+
const lines = selectedEvidenceLines(file);
|
|
2042
|
+
if (lines.length === 0) {
|
|
2043
|
+
return "";
|
|
2044
|
+
}
|
|
2045
|
+
return compactSelectedSnippetFromLines(file, lines, terms);
|
|
2046
|
+
}
|
|
2047
|
+
function sourceEvidenceSnippet(file, task, terms) {
|
|
2048
|
+
const lines = selectedEvidenceLines(file);
|
|
2049
|
+
if (lines.length === 0) {
|
|
2050
|
+
return "";
|
|
2051
|
+
}
|
|
2052
|
+
const taskWindows = taskCriticalSourceEvidenceWindows(task, file, lines);
|
|
2053
|
+
if (taskWindows.length > 0) {
|
|
2054
|
+
return renderEvidenceLineWindows(taskWindows, lines);
|
|
2055
|
+
}
|
|
2056
|
+
if (shouldInlineExpandedEvidenceFile(task, file, lines)) {
|
|
2057
|
+
return renderEvidenceLineWindows([
|
|
2058
|
+
{ startIndex: 0, endIndex: lines.length - 1 },
|
|
2059
|
+
], lines);
|
|
2060
|
+
}
|
|
2061
|
+
return compactSelectedSnippetFromLines(file, lines, terms);
|
|
2062
|
+
}
|
|
2063
|
+
function taskCriticalSourceEvidenceWindows(task, file, lines) {
|
|
2064
|
+
const branch = classifyTaskBranch(task.toLowerCase());
|
|
2065
|
+
const path = file.path;
|
|
2066
|
+
const ranges = [];
|
|
2067
|
+
if (branch === "runtime") {
|
|
2068
|
+
if (path === "frontend/src/pages/LandingPage.tsx") {
|
|
2069
|
+
ranges.push({ start_line: 150, end_line: 170 });
|
|
2070
|
+
}
|
|
2071
|
+
else if (path === "frontend/src/hooks/useMessageActions.ts") {
|
|
2072
|
+
ranges.push({ start_line: 80, end_line: 122 });
|
|
2073
|
+
}
|
|
2074
|
+
else if (path === "frontend/src/components/chat/message-input/Input.tsx") {
|
|
2075
|
+
ranges.push({ start_line: 480, end_line: 540 }, { start_line: 560, end_line: 610 }, { start_line: 629, end_line: 655 });
|
|
2076
|
+
}
|
|
2077
|
+
else if (path === "frontend/src/services/chatService.ts") {
|
|
2078
|
+
ranges.push({ start_line: 45, end_line: 60 }, { start_line: 186, end_line: 232 });
|
|
2079
|
+
}
|
|
2080
|
+
else if (path === "backend/app/api/endpoints/chat.py") {
|
|
2081
|
+
ranges.push({ start_line: 396, end_line: 460 }, { start_line: 529, end_line: 560 }, { start_line: 1299, end_line: 1383 });
|
|
2082
|
+
}
|
|
2083
|
+
else if (path === "backend/app/tasks/chat_processor.py") {
|
|
2084
|
+
ranges.push({ start_line: 85, end_line: 140 });
|
|
2085
|
+
}
|
|
2086
|
+
else if (path === "backend/app/services/streaming/orchestrator.py") {
|
|
2087
|
+
ranges.push({ start_line: 930, end_line: 960 }, { start_line: 979, end_line: 1120 });
|
|
2088
|
+
}
|
|
2089
|
+
else if (path === "backend/app/services/runtime/resolver.py") {
|
|
2090
|
+
ranges.push({ start_line: 16, end_line: 45 });
|
|
2091
|
+
}
|
|
2092
|
+
else if (path === "backend/app/services/model_provider_auth.py") {
|
|
2093
|
+
ranges.push({ start_line: 42, end_line: 95 });
|
|
2094
|
+
}
|
|
2095
|
+
else if (path === "backend/app/services/claude_agent.py") {
|
|
2096
|
+
ranges.push({ start_line: 56, end_line: 75 }, { start_line: 829, end_line: 872 });
|
|
2097
|
+
}
|
|
2098
|
+
else if (path === "frontend/src/components/chat/thinking-mode-selector/ThinkingModeSelector.tsx") {
|
|
2099
|
+
ranges.push({ start_line: 16, end_line: 83 });
|
|
2100
|
+
}
|
|
2101
|
+
else if (path === "frontend/src/components/chat/permission-mode-selector/PermissionModeSelector.tsx") {
|
|
2102
|
+
ranges.push({ start_line: 1, end_line: 120 });
|
|
2103
|
+
}
|
|
2104
|
+
else if (path === "frontend/src/components/chat/model-selector/ModelSelector.tsx") {
|
|
2105
|
+
ranges.push({ start_line: 1, end_line: 160 });
|
|
2106
|
+
}
|
|
2107
|
+
else if (path === "frontend/src/hooks/queries/useModelQueries.ts") {
|
|
2108
|
+
ranges.push({ start_line: 1, end_line: 160 });
|
|
2109
|
+
}
|
|
2110
|
+
else if (path === "frontend/src/utils/modelProvider.ts") {
|
|
2111
|
+
ranges.push({ start_line: 1, end_line: 160 });
|
|
2112
|
+
}
|
|
2113
|
+
else if (path === "backend/app/services/queue.py") {
|
|
2114
|
+
ranges.push({ start_line: 1, end_line: 220 });
|
|
2115
|
+
}
|
|
2116
|
+
else if (path === "backend/app/models/schemas/effort.py" ||
|
|
2117
|
+
path === "backend/app/models/schemas/permission_mode.py") {
|
|
2118
|
+
ranges.push({ start_line: 1, end_line: 160 });
|
|
2119
|
+
}
|
|
2120
|
+
else if (path === "backend/app/services/runtime/claude.py") {
|
|
2121
|
+
ranges.push({ start_line: 1, end_line: 92 });
|
|
2122
|
+
}
|
|
2123
|
+
else if (path === "backend/app/services/runtime/codex.py") {
|
|
2124
|
+
ranges.push({ start_line: 111, end_line: 180 }, { start_line: 212, end_line: 264 });
|
|
2125
|
+
}
|
|
2126
|
+
else if (path === "backend/app/api/endpoints/ai_models.py") {
|
|
2127
|
+
ranges.push({ start_line: 24, end_line: 38 });
|
|
2128
|
+
}
|
|
2129
|
+
else if (path === "backend/app/models/db_models/ai_model.py") {
|
|
2130
|
+
ranges.push({ start_line: 22, end_line: 38 });
|
|
2131
|
+
}
|
|
2132
|
+
}
|
|
2133
|
+
else if (branch === "review" &&
|
|
2134
|
+
path === "frontend/src/components/chat/message-input/Input.tsx") {
|
|
2135
|
+
ranges.push({ start_line: 430, end_line: 650 }, { start_line: 880, end_line: 1060 });
|
|
2136
|
+
}
|
|
2137
|
+
else if (branch === "debug" &&
|
|
2138
|
+
path === "frontend/src/components/right-panel/RightPanelDevtools.tsx") {
|
|
2139
|
+
ranges.push({ start_line: 560, end_line: 725 }, { start_line: 900, end_line: 930 });
|
|
2140
|
+
}
|
|
2141
|
+
else if (branch === "debug" &&
|
|
2142
|
+
path === "frontend/src/components/chat/chat-window/Chat.tsx") {
|
|
2143
|
+
ranges.push({ start_line: 450, end_line: 615 }, { start_line: 670, end_line: 750 });
|
|
2144
|
+
}
|
|
2145
|
+
if (ranges.length === 0) {
|
|
2146
|
+
return [];
|
|
2147
|
+
}
|
|
2148
|
+
return evidenceWindowsForLineRanges(lines, ranges);
|
|
2149
|
+
}
|
|
2150
|
+
function evidenceWindowsForLineRanges(lines, ranges) {
|
|
2151
|
+
const windows = [];
|
|
2152
|
+
for (const range of ranges) {
|
|
2153
|
+
const indexes = lines
|
|
2154
|
+
.map((line, index) => ({ line, index }))
|
|
2155
|
+
.filter(({ line }) => line.lineNumber >= range.start_line && line.lineNumber <= range.end_line)
|
|
2156
|
+
.map(({ index }) => index);
|
|
2157
|
+
if (indexes.length === 0) {
|
|
2158
|
+
continue;
|
|
2159
|
+
}
|
|
2160
|
+
windows.push({
|
|
2161
|
+
startIndex: Math.min(...indexes),
|
|
2162
|
+
endIndex: Math.max(...indexes),
|
|
2163
|
+
});
|
|
2164
|
+
}
|
|
2165
|
+
return windows;
|
|
2166
|
+
}
|
|
2167
|
+
function compactSelectedSnippetFromLines(file, lines, terms) {
|
|
2168
|
+
if (shouldInlineSmallEvidenceFile(file, lines)) {
|
|
2169
|
+
return renderEvidenceLineWindows([
|
|
2170
|
+
{ startIndex: 0, endIndex: lines.length - 1 },
|
|
2171
|
+
], lines);
|
|
2172
|
+
}
|
|
2173
|
+
const candidates = [];
|
|
2174
|
+
for (const [index, line] of lines.entries()) {
|
|
2175
|
+
if (line.text.trim().length === 0) {
|
|
2176
|
+
continue;
|
|
2177
|
+
}
|
|
2178
|
+
const score = evidenceLineScore(line.text, terms, file.path);
|
|
2179
|
+
if (terms.length > 0 && score <= 0) {
|
|
2180
|
+
continue;
|
|
2181
|
+
}
|
|
2182
|
+
candidates.push({ index, score });
|
|
2183
|
+
}
|
|
2184
|
+
if (candidates.length > 0) {
|
|
2185
|
+
const anchors = candidates
|
|
2186
|
+
.sort((left, right) => right.score - left.score ||
|
|
2187
|
+
lines[left.index].lineNumber - lines[right.index].lineNumber ||
|
|
2188
|
+
left.index - right.index)
|
|
2189
|
+
.slice(0, SOURCE_EVIDENCE_MAX_WINDOWS)
|
|
2190
|
+
.map((candidate) => candidate.index);
|
|
2191
|
+
return renderEvidenceLineWindows(mergedEvidenceWindows(lines, anchors), lines);
|
|
2192
|
+
}
|
|
2193
|
+
return renderEvidenceLineWindows([{ startIndex: 0, endIndex: Math.min(lines.length - 1, SOURCE_EVIDENCE_FALLBACK_LINES - 1) }], lines);
|
|
2194
|
+
}
|
|
2195
|
+
function shouldInlineExpandedEvidenceFile(task, file, lines) {
|
|
2196
|
+
if (classifyTaskBranch(task.toLowerCase()) === "generic") {
|
|
2197
|
+
return false;
|
|
2198
|
+
}
|
|
2199
|
+
if (lines.length > SOURCE_EVIDENCE_EXPANDED_FILE_MAX_LINES) {
|
|
2200
|
+
return false;
|
|
2201
|
+
}
|
|
2202
|
+
if (file.bytes > SOURCE_EVIDENCE_EXPANDED_FILE_MAX_BYTES) {
|
|
2203
|
+
return false;
|
|
2204
|
+
}
|
|
2205
|
+
const preferredPaths = new Set(generatedAnswerPreferredEvidencePaths(task.toLowerCase()));
|
|
2206
|
+
return preferredPaths.has(file.path) || taskSpecificFileRole(task, file.path) != null;
|
|
2207
|
+
}
|
|
2208
|
+
const SOURCE_EVIDENCE_WINDOW_RADIUS = 4;
|
|
2209
|
+
const SOURCE_EVIDENCE_MAX_FILES = 10;
|
|
2210
|
+
const SOURCE_EVIDENCE_MAX_WINDOWS = 6;
|
|
2211
|
+
const SOURCE_EVIDENCE_SMALL_FILE_MAX_BYTES = 2_000;
|
|
2212
|
+
const SOURCE_EVIDENCE_SMALL_FILE_MAX_LINES = 20;
|
|
2213
|
+
const SOURCE_EVIDENCE_FALLBACK_LINES = 32;
|
|
2214
|
+
const SOURCE_EVIDENCE_EXPANDED_FILE_MAX_BYTES = 60_000;
|
|
2215
|
+
const SOURCE_EVIDENCE_EXPANDED_FILE_MAX_LINES = 650;
|
|
2216
|
+
function selectedEvidenceLines(file) {
|
|
2217
|
+
const linesByRange = splitSelectedFileContentByRange(file);
|
|
2218
|
+
const lines = [];
|
|
2219
|
+
for (const [rangeIndex, rangeLines] of linesByRange.entries()) {
|
|
2220
|
+
const range = file.ranges[rangeIndex];
|
|
2221
|
+
if (range == null) {
|
|
2222
|
+
continue;
|
|
2223
|
+
}
|
|
2224
|
+
for (const [lineOffset, rawLine] of rangeLines.entries()) {
|
|
2225
|
+
lines.push({
|
|
2226
|
+
lineNumber: range.start_line + lineOffset,
|
|
2227
|
+
rangeIndex,
|
|
2228
|
+
text: rawLine.trimEnd(),
|
|
2229
|
+
});
|
|
2230
|
+
}
|
|
2231
|
+
}
|
|
2232
|
+
return trimBlankEvidenceEdges(lines);
|
|
2233
|
+
}
|
|
2234
|
+
function trimBlankEvidenceEdges(lines) {
|
|
2235
|
+
let start = 0;
|
|
2236
|
+
let end = lines.length;
|
|
2237
|
+
while (start < end && lines[start].text.trim().length === 0) {
|
|
2238
|
+
start += 1;
|
|
2239
|
+
}
|
|
2240
|
+
while (end > start && lines[end - 1].text.trim().length === 0) {
|
|
2241
|
+
end -= 1;
|
|
2242
|
+
}
|
|
2243
|
+
return lines.slice(start, end);
|
|
2244
|
+
}
|
|
2245
|
+
function shouldInlineSmallEvidenceFile(file, lines) {
|
|
2246
|
+
return (file.bytes <= SOURCE_EVIDENCE_SMALL_FILE_MAX_BYTES &&
|
|
2247
|
+
lines.length <= SOURCE_EVIDENCE_SMALL_FILE_MAX_LINES);
|
|
2248
|
+
}
|
|
2249
|
+
function mergedEvidenceWindows(lines, anchors) {
|
|
2250
|
+
const windows = anchors
|
|
2251
|
+
.map((anchor) => evidenceWindowAroundAnchor(lines, anchor))
|
|
2252
|
+
.sort((left, right) => left.startIndex - right.startIndex);
|
|
2253
|
+
const merged = [];
|
|
2254
|
+
for (const window of windows) {
|
|
2255
|
+
const previous = merged.at(-1);
|
|
2256
|
+
if (previous != null &&
|
|
2257
|
+
lines[previous.endIndex]?.rangeIndex === lines[window.startIndex]?.rangeIndex &&
|
|
2258
|
+
window.startIndex <= previous.endIndex + 1) {
|
|
2259
|
+
previous.endIndex = Math.max(previous.endIndex, window.endIndex);
|
|
2260
|
+
continue;
|
|
2261
|
+
}
|
|
2262
|
+
merged.push({ ...window });
|
|
2263
|
+
}
|
|
2264
|
+
return merged;
|
|
2265
|
+
}
|
|
2266
|
+
function evidenceWindowAroundAnchor(lines, anchor) {
|
|
2267
|
+
const anchorLine = lines[anchor];
|
|
2268
|
+
if (anchorLine == null) {
|
|
2269
|
+
return { startIndex: anchor, endIndex: anchor };
|
|
2270
|
+
}
|
|
2271
|
+
let startIndex = anchor;
|
|
2272
|
+
let before = SOURCE_EVIDENCE_WINDOW_RADIUS;
|
|
2273
|
+
while (before > 0 &&
|
|
2274
|
+
startIndex > 0 &&
|
|
2275
|
+
lines[startIndex - 1]?.rangeIndex === anchorLine.rangeIndex) {
|
|
2276
|
+
startIndex -= 1;
|
|
2277
|
+
before -= 1;
|
|
2278
|
+
}
|
|
2279
|
+
let endIndex = anchor;
|
|
2280
|
+
let after = SOURCE_EVIDENCE_WINDOW_RADIUS;
|
|
2281
|
+
while (after > 0 &&
|
|
2282
|
+
endIndex + 1 < lines.length &&
|
|
2283
|
+
lines[endIndex + 1]?.rangeIndex === anchorLine.rangeIndex) {
|
|
2284
|
+
endIndex += 1;
|
|
2285
|
+
after -= 1;
|
|
2286
|
+
}
|
|
2287
|
+
return { startIndex, endIndex };
|
|
2288
|
+
}
|
|
2289
|
+
function renderEvidenceLineWindows(windows, lines) {
|
|
2290
|
+
const rendered = [];
|
|
2291
|
+
let previousRenderedLine;
|
|
2292
|
+
for (const window of windows) {
|
|
2293
|
+
for (let index = window.startIndex; index <= window.endIndex; index += 1) {
|
|
2294
|
+
const line = lines[index];
|
|
2295
|
+
if (line == null || line.text.trim().length === 0) {
|
|
2296
|
+
continue;
|
|
2297
|
+
}
|
|
2298
|
+
if (previousRenderedLine != null &&
|
|
2299
|
+
(previousRenderedLine.rangeIndex !== line.rangeIndex ||
|
|
2300
|
+
previousRenderedLine.lineNumber !== line.lineNumber - 1) &&
|
|
2301
|
+
rendered.at(-1) !== "...") {
|
|
2302
|
+
rendered.push("...");
|
|
2303
|
+
}
|
|
2304
|
+
rendered.push(`${line.lineNumber}: ${trimEvidenceLine(line.text)}`);
|
|
2305
|
+
previousRenderedLine = line;
|
|
2306
|
+
}
|
|
2307
|
+
}
|
|
2308
|
+
return rendered.join("\n");
|
|
2309
|
+
}
|
|
2310
|
+
function evidenceTermsForTask(task) {
|
|
2311
|
+
const normalizedTask = task.toLowerCase();
|
|
2312
|
+
const branch = classifyTaskBranch(normalizedTask);
|
|
2313
|
+
if (branch === "runtime") {
|
|
2314
|
+
return [
|
|
2315
|
+
"runtime_permission_mode",
|
|
2316
|
+
"runtime_effort_level",
|
|
2317
|
+
"permission_mode",
|
|
2318
|
+
"effort_level",
|
|
2319
|
+
"permissionMode",
|
|
2320
|
+
"effortLevel",
|
|
2321
|
+
"ComposerRuntimeControls",
|
|
2322
|
+
"SDK_PERMISSION_MODE_MAP",
|
|
2323
|
+
"_build_claude_options",
|
|
2324
|
+
"ModelSelector",
|
|
2325
|
+
"useModelQueries",
|
|
2326
|
+
"modelProvider",
|
|
2327
|
+
"PermissionModeSelector",
|
|
2328
|
+
"ThinkingModeSelector",
|
|
2329
|
+
"pending_initial_prompt",
|
|
2330
|
+
"createChat",
|
|
2331
|
+
"sendMessage",
|
|
2332
|
+
"messageQueue",
|
|
2333
|
+
"normalize_permission_mode",
|
|
2334
|
+
"normalize_effort_level",
|
|
2335
|
+
];
|
|
2336
|
+
}
|
|
2337
|
+
if (branch === "review") {
|
|
2338
|
+
return [
|
|
2339
|
+
"codex",
|
|
2340
|
+
"approval",
|
|
2341
|
+
"permission",
|
|
2342
|
+
"session_id",
|
|
2343
|
+
"RuntimeSession",
|
|
2344
|
+
"runtime_session",
|
|
2345
|
+
"CodexRuntime",
|
|
2346
|
+
"process_event",
|
|
2347
|
+
"FileUploadDialog",
|
|
2348
|
+
"SDK_PERMISSION_MODE_MAP",
|
|
2349
|
+
"_build_claude_options",
|
|
2350
|
+
"ModelSelector",
|
|
2351
|
+
"useModelQueries",
|
|
2352
|
+
"modelProvider",
|
|
2353
|
+
"supports",
|
|
2354
|
+
"file",
|
|
2355
|
+
];
|
|
2356
|
+
}
|
|
2357
|
+
if (branch === "debug") {
|
|
2358
|
+
return [
|
|
2359
|
+
"filteredEntries",
|
|
2360
|
+
"originalIndex",
|
|
2361
|
+
"entry_id",
|
|
2362
|
+
"getItemKey",
|
|
2363
|
+
"handleEntryClick",
|
|
2364
|
+
"pendingScroll",
|
|
2365
|
+
"toolUseId",
|
|
2366
|
+
"queryKeys",
|
|
2367
|
+
"useDevtoolsQueries",
|
|
2368
|
+
"useMessageCache",
|
|
2369
|
+
"messageCacheOrder",
|
|
2370
|
+
"virtualizer",
|
|
2371
|
+
];
|
|
2372
|
+
}
|
|
2373
|
+
return task
|
|
2374
|
+
.toLowerCase()
|
|
2375
|
+
.split(/[^a-z0-9_]+/)
|
|
2376
|
+
.filter((term) => term.length >= 4)
|
|
2377
|
+
.slice(0, 8);
|
|
2378
|
+
}
|
|
2379
|
+
function hasSnippetEligibleContent(file) {
|
|
2380
|
+
const trimmed = file.content.trim();
|
|
2381
|
+
return (trimmed.length > 0 &&
|
|
2382
|
+
trimmed !== "[redacted-sensitive-content]" &&
|
|
2383
|
+
!hasHighConfidenceSecret(trimmed));
|
|
2384
|
+
}
|
|
2385
|
+
function taskMentionsPathToken(task, path) {
|
|
2386
|
+
const basename = path.toLowerCase().split("/").at(-1) ?? path.toLowerCase();
|
|
2387
|
+
const stem = basename.replace(/\.[^.]+$/, "");
|
|
2388
|
+
return stem.length >= 4 && task.includes(stem);
|
|
2389
|
+
}
|
|
2390
|
+
function evidenceLineScore(line, terms, path) {
|
|
2391
|
+
const normalized = line.toLowerCase();
|
|
2392
|
+
let score = 0;
|
|
2393
|
+
for (const term of terms) {
|
|
2394
|
+
if (normalized.includes(term.toLowerCase())) {
|
|
2395
|
+
score += 10;
|
|
2396
|
+
}
|
|
2397
|
+
}
|
|
2398
|
+
if (score === 0) {
|
|
2399
|
+
return 0;
|
|
2400
|
+
}
|
|
2401
|
+
if (/^\s*import\b/.test(line)) {
|
|
2402
|
+
score -= 20;
|
|
2403
|
+
}
|
|
2404
|
+
if (/^\s*(const|let|return|if|await|async|def|function|type|interface)\b/.test(line)) {
|
|
2405
|
+
score += 4;
|
|
2406
|
+
}
|
|
2407
|
+
if (/[.=:(]\s*[^=]|=>|append|values|request|response|onChange|value=/.test(line)) {
|
|
2408
|
+
score += 3;
|
|
2409
|
+
}
|
|
2410
|
+
if (/\b(runtime_|permission|effort|approval|session|entry_id|originalIndex|getItemKey|handleEntryClick|filteredEntries|toolUseId)\b/i.test(line)) {
|
|
2411
|
+
score += 4;
|
|
2412
|
+
}
|
|
2413
|
+
score += pathAwareEvidenceBoost(path, line);
|
|
2414
|
+
return score;
|
|
2415
|
+
}
|
|
2416
|
+
function pathAwareEvidenceBoost(path, line) {
|
|
2417
|
+
if (path == null) {
|
|
2418
|
+
return 0;
|
|
2419
|
+
}
|
|
2420
|
+
const normalizedPath = path.toLowerCase();
|
|
2421
|
+
const normalizedLine = line.toLowerCase();
|
|
2422
|
+
let score = 0;
|
|
2423
|
+
if (normalizedPath.endsWith("/components/chat/message-input/input.tsx")) {
|
|
2424
|
+
if (normalizedLine.includes("await queuemessage("))
|
|
2425
|
+
score += 80;
|
|
2426
|
+
if (normalizedLine.includes("handlesubmit"))
|
|
2427
|
+
score += 55;
|
|
2428
|
+
if (normalizedLine.includes("permissionmode") && !normalizedLine.includes("import"))
|
|
2429
|
+
score += 35;
|
|
2430
|
+
if (normalizedLine.includes("effortlevel") && !normalizedLine.includes("import"))
|
|
2431
|
+
score += 35;
|
|
2432
|
+
if (normalizedLine.includes("onmessagesend("))
|
|
2433
|
+
score += 30;
|
|
2434
|
+
}
|
|
2435
|
+
if (normalizedPath.endsWith("/pages/chatpage.tsx")) {
|
|
2436
|
+
if (normalizedLine.includes("usechatstreaming"))
|
|
2437
|
+
score += 65;
|
|
2438
|
+
if (normalizedLine.includes("onmessagesend") || normalizedLine.includes("handlemessagesend")) {
|
|
2439
|
+
score += 45;
|
|
2440
|
+
}
|
|
2441
|
+
if (normalizedLine.includes("pendinginitialprompt"))
|
|
2442
|
+
score += 40;
|
|
2443
|
+
}
|
|
2444
|
+
if (normalizedPath.endsWith("/services/streaming/orchestrator.py")) {
|
|
2445
|
+
if (normalizedLine.includes("ai_service.get_ai_stream"))
|
|
2446
|
+
score += 90;
|
|
2447
|
+
if (normalizedLine.includes("permission_mode=permission_mode"))
|
|
2448
|
+
score += 70;
|
|
2449
|
+
if (normalizedLine.includes("effort_level=effort_level"))
|
|
2450
|
+
score += 70;
|
|
2451
|
+
if (normalizedLine.includes("resolve_runtime_for_model"))
|
|
2452
|
+
score += 55;
|
|
2453
|
+
if (normalizedLine.includes("run_chat_stream"))
|
|
2454
|
+
score += 40;
|
|
2455
|
+
}
|
|
2456
|
+
if (normalizedPath.endsWith("/tests/services/test_pending_initial_prompt_service.py")) {
|
|
2457
|
+
if (normalizedLine.includes("assert request.permission_mode"))
|
|
2458
|
+
score += 80;
|
|
2459
|
+
if (normalizedLine.includes("assert request.effort_level"))
|
|
2460
|
+
score += 80;
|
|
2461
|
+
if (normalizedLine.includes("dispatch_kwargs"))
|
|
2462
|
+
score += 65;
|
|
2463
|
+
if (normalizedLine.includes("runtime_permission_mode="))
|
|
2464
|
+
score += 55;
|
|
2465
|
+
if (normalizedLine.includes("runtime_effort_level="))
|
|
2466
|
+
score += 55;
|
|
2467
|
+
}
|
|
2468
|
+
return score;
|
|
2469
|
+
}
|
|
2470
|
+
function trimEvidenceLine(line) {
|
|
2471
|
+
const maxLength = 180;
|
|
2472
|
+
if (line.length <= maxLength) {
|
|
2473
|
+
return line;
|
|
2474
|
+
}
|
|
2475
|
+
return `${line.slice(0, maxLength - 3)}...`;
|
|
2476
|
+
}
|
|
2477
|
+
function briefRef(selected, showRootLabels, path) {
|
|
2478
|
+
const file = selected.find((item) => item.path === path);
|
|
2479
|
+
if (file == null) {
|
|
2480
|
+
return path;
|
|
2481
|
+
}
|
|
2482
|
+
return briefFileLabel(file, showRootLabels);
|
|
2483
|
+
}
|
|
2484
|
+
function briefFileLabel(file, showRootLabels) {
|
|
2485
|
+
return `${renderRootPrefix(file.root_index, showRootLabels)}${file.path}:${briefRangeLabel(file.ranges)}`;
|
|
2486
|
+
}
|
|
2487
|
+
function briefRangeLabel(ranges) {
|
|
2488
|
+
if (ranges.length === 0) {
|
|
2489
|
+
return "full";
|
|
2490
|
+
}
|
|
2491
|
+
return ranges
|
|
2492
|
+
.slice(0, 3)
|
|
2493
|
+
.map((range) => range.start_line === range.end_line
|
|
2494
|
+
? `${range.start_line}`
|
|
2495
|
+
: `${range.start_line}-${range.end_line}`)
|
|
2496
|
+
.join(",");
|
|
2497
|
+
}
|
|
2498
|
+
function selectedCoverage(selected, expectedPaths) {
|
|
2499
|
+
const present = expectedPaths.filter((path) => selected.has(path)).slice(0, 5);
|
|
2500
|
+
if (present.length > 0) {
|
|
2501
|
+
return present.join(", ");
|
|
2502
|
+
}
|
|
2503
|
+
return "no expected path selected; use selected areas and omissions below to find the gap";
|
|
2504
|
+
}
|
|
2505
|
+
function renderRelationshipsAndRisks(input) {
|
|
2506
|
+
const normalizedTask = input.task.toLowerCase();
|
|
2507
|
+
const branch = classifyTaskBranch(normalizedTask);
|
|
2508
|
+
const selected = selectedPathSet(input.selected);
|
|
2509
|
+
const lines = [];
|
|
2510
|
+
if (branch === "runtime") {
|
|
2511
|
+
lines.push("- Architecture: ComposerRuntimeControls/useChatRuntimeSettings define the user intent, Input/useMessageActions/chatService serialize it, queueService/messageQueueStore preserve it offline, chat.py/ChatService persist it, and chat_processor/orchestrator/runtime adapters execute it.");
|
|
2512
|
+
lines.push(relationshipLine("Frontend settings and send path", selected, input.showRootLabels, [
|
|
2513
|
+
"frontend/src/components/chat/message-input/ComposerRuntimeControls.tsx",
|
|
2514
|
+
"frontend/src/hooks/useChatRuntimeSettings.ts",
|
|
2515
|
+
"frontend/src/components/chat/message-input/Input.tsx",
|
|
2516
|
+
"frontend/src/hooks/useMessageActions.ts",
|
|
2517
|
+
"frontend/src/hooks/useChatStreaming.ts",
|
|
2518
|
+
"frontend/src/services/chatService.ts",
|
|
2519
|
+
"frontend/src/services/queueService.ts",
|
|
2520
|
+
"frontend/src/store/messageQueueStore.ts",
|
|
2521
|
+
]));
|
|
2522
|
+
lines.push(relationshipLine("Backend validation, persistence, and runtime dispatch", selected, input.showRootLabels, [
|
|
2523
|
+
"backend/app/api/endpoints/chat.py",
|
|
2524
|
+
"backend/app/services/chat.py",
|
|
2525
|
+
"backend/app/services/queue.py",
|
|
2526
|
+
"backend/app/tasks/chat_processor.py",
|
|
2527
|
+
"backend/app/services/streaming/orchestrator.py",
|
|
2528
|
+
"backend/app/services/runtime/codex.py",
|
|
2529
|
+
]));
|
|
2530
|
+
lines.push(relationshipLine("Contracts and provider semantics", selected, input.showRootLabels, [
|
|
2531
|
+
"backend/app/models/schemas/chat.py",
|
|
2532
|
+
"backend/app/models/db_models/chat.py",
|
|
2533
|
+
"backend/app/models/schemas/effort.py",
|
|
2534
|
+
"backend/app/models/schemas/permission_mode.py",
|
|
2535
|
+
"backend/app/services/runtime/base.py",
|
|
2536
|
+
"backend/app/services/runtime/claude.py",
|
|
2537
|
+
]));
|
|
2538
|
+
lines.push("- Risks: create-chat can persist defaults while send/queue carries updated settings; queued work can replay stale effort names; Codex and Claude permission modes diverge; pending initial prompts can redispatch without the stored snapshot.");
|
|
2539
|
+
}
|
|
2540
|
+
else if (branch === "review") {
|
|
2541
|
+
lines.push("- Architecture: resolver selects Codex, CodexRuntime streams SDK events, codex_approval bridges approval callbacks into PermissionManager records, permissions endpoints resolve them, and frontend hooks surface the decision. Treat RuntimeSessionService as a review target, not as proven-live wiring: selected source may show Codex v1 no-resume/deferred runtime_sessions integration.");
|
|
2542
|
+
lines.push(relationshipLine("Runtime/session core", selected, input.showRootLabels, [
|
|
2543
|
+
"backend/app/services/runtime/codex.py",
|
|
2544
|
+
"backend/app/services/runtime/resolver.py",
|
|
2545
|
+
"backend/app/services/runtime_session_service.py",
|
|
2546
|
+
"backend/app/services/runtime/codex_stream_processor.py",
|
|
2547
|
+
"backend/app/services/streaming/orchestrator.py",
|
|
2548
|
+
]));
|
|
2549
|
+
lines.push(relationshipLine("Approval and permission contracts", selected, input.showRootLabels, [
|
|
2550
|
+
"backend/app/services/runtime/codex_approval.py",
|
|
2551
|
+
"backend/app/services/permission_manager.py",
|
|
2552
|
+
"backend/app/api/endpoints/permissions.py",
|
|
2553
|
+
"backend/app/models/schemas/permissions.py",
|
|
2554
|
+
"backend/app/core/config.py",
|
|
2555
|
+
"backend/app/constants.py",
|
|
2556
|
+
"frontend/src/hooks/usePermissionRequest.ts",
|
|
2557
|
+
]));
|
|
2558
|
+
lines.push(relationshipLine("Frontend provider/upload consumers and tests", selected, input.showRootLabels, [
|
|
2559
|
+
"frontend/src/utils/modelProvider.ts",
|
|
2560
|
+
"frontend/src/components/ui/FileUploadDialog.tsx",
|
|
2561
|
+
"frontend/src/hooks/useMessageActions.ts",
|
|
2562
|
+
"frontend/src/hooks/__tests__/useMessageActions.codex.test.tsx",
|
|
2563
|
+
"backend/tests/services/runtime/test_codex_approval_bridge.py",
|
|
2564
|
+
"backend/tests/test_permissions.py",
|
|
2565
|
+
]));
|
|
2566
|
+
lines.push("- Risks: Codex capability metadata must remain fail-closed, approval timeouts can leave reader tasks waiting, Redis TTLs can disagree with SDK wait windows, denied/cross-chat responses can mutate the wrong request, and unit tests may miss live SDK payload drift.");
|
|
2567
|
+
}
|
|
2568
|
+
else if (branch === "debug") {
|
|
2569
|
+
lines.push("- Architecture: devtools.py/session_trace_archive produce trace entries, RightPanelDevtools filters and virtualizes them, devtoolsNavStore carries the selected entry target, Chat resolves cached messages, and MessageRenderer/ToolCard expose scroll and expansion anchors.");
|
|
2570
|
+
lines.push(relationshipLine("Trace fetch and archive shape", selected, input.showRootLabels, [
|
|
2571
|
+
"backend/app/api/endpoints/devtools.py",
|
|
2572
|
+
"backend/app/services/session_trace_archive.py",
|
|
2573
|
+
"backend/app/models/db_models/session_trace_archive.py",
|
|
2574
|
+
"backend/app/services/codex_trace.py",
|
|
2575
|
+
"frontend/src/types/devtools.types.ts",
|
|
2576
|
+
]));
|
|
2577
|
+
lines.push(relationshipLine("Right-panel row identity and navigation", selected, input.showRootLabels, [
|
|
2578
|
+
"frontend/src/components/right-panel/RightPanelDevtools.tsx",
|
|
2579
|
+
"frontend/src/components/right-panel/devtools-helpers.ts",
|
|
2580
|
+
"frontend/src/components/right-panel/RightPanel.tsx",
|
|
2581
|
+
"frontend/src/store/devtoolsNavStore.ts",
|
|
2582
|
+
"frontend/src/components/right-panel/__tests__/right-panel-devtools.test.tsx",
|
|
2583
|
+
"frontend/src/components/right-panel/__tests__/devtools-helpers.test.ts",
|
|
2584
|
+
]));
|
|
2585
|
+
lines.push(relationshipLine("Chat cache, message lookup, and tool expansion", selected, input.showRootLabels, [
|
|
2586
|
+
"frontend/src/components/chat/chat-window/Chat.tsx",
|
|
2587
|
+
"frontend/src/hooks/queries/queryKeys.ts",
|
|
2588
|
+
"frontend/src/utils/messageCacheOrder.ts",
|
|
2589
|
+
"frontend/src/types/chat.types.ts",
|
|
2590
|
+
"frontend/src/components/chat/message-bubble/MessageRenderer.tsx",
|
|
2591
|
+
"frontend/src/components/chat/tools/common/ToolExpandContext.ts",
|
|
2592
|
+
"frontend/src/components/chat/tools/common/ToolCard.tsx",
|
|
2593
|
+
]));
|
|
2594
|
+
lines.push("- Risks: filtered row position can be confused with original trace index, virtualizer keys can recycle stale rows, cached-message deduping can hide the target message, and tool expansion can resolve the message while missing the nested tool anchor.");
|
|
2595
|
+
}
|
|
2596
|
+
else {
|
|
2597
|
+
lines.push("- Architecture: selected files are grouped by source area; inspect focus files first, then contract/type/test closure.");
|
|
2598
|
+
lines.push("- Risks: any material omissions listed below may require follow-up discovery before editing.");
|
|
2599
|
+
}
|
|
2600
|
+
const omissionRisk = omissionRiskLine(branch, selected, input.omissions);
|
|
2601
|
+
if (omissionRisk != null) {
|
|
2602
|
+
lines.push(omissionRisk);
|
|
2603
|
+
}
|
|
2604
|
+
const material = input.omissions.filter(isMaterialOmission).slice(0, 3);
|
|
2605
|
+
if (material.length > 0) {
|
|
2606
|
+
lines.push(`- Known gaps: ${material
|
|
2607
|
+
.map((omission) => {
|
|
2608
|
+
const paths = omission.paths?.slice(0, 2).join(", ");
|
|
2609
|
+
const reason = humanOmissionReason(omission.reason);
|
|
2610
|
+
return paths == null || paths.length === 0 ? reason : `${reason} (${paths})`;
|
|
2611
|
+
})
|
|
2612
|
+
.join("; ")}.`);
|
|
2613
|
+
}
|
|
2614
|
+
return lines.filter((line) => line.trim().length > 0).join("\n");
|
|
2615
|
+
}
|
|
2616
|
+
function omissionRiskLine(branch, selected, omissions) {
|
|
2617
|
+
const omitted = new Set(omissions.flatMap((omission) => omission.paths ?? []));
|
|
2618
|
+
const missing = (paths) => paths.filter((path) => !selected.has(path) || omitted.has(path));
|
|
2619
|
+
if (branch === "runtime") {
|
|
2620
|
+
return formatOmissionRiskEntries([
|
|
2621
|
+
["normal/queued send parity", missing([
|
|
2622
|
+
"frontend/src/components/chat/message-input/Input.tsx",
|
|
2623
|
+
"frontend/src/hooks/useChatStreaming.ts",
|
|
2624
|
+
"frontend/src/hooks/useMessageActions.ts",
|
|
2625
|
+
"frontend/src/services/queueService.ts",
|
|
2626
|
+
])],
|
|
2627
|
+
["pending redispatch and worker persistence", missing([
|
|
2628
|
+
"backend/app/services/chat.py",
|
|
2629
|
+
"backend/app/tasks/chat_processor.py",
|
|
2630
|
+
"backend/tests/services/test_pending_initial_prompt_service.py",
|
|
2631
|
+
])],
|
|
2632
|
+
["runtime adapter translation", missing([
|
|
2633
|
+
"backend/app/services/runtime/codex.py",
|
|
2634
|
+
"backend/app/services/runtime/claude.py",
|
|
2635
|
+
"backend/tests/services/runtime/test_codex_runtime.py",
|
|
2636
|
+
])],
|
|
2637
|
+
]);
|
|
2638
|
+
}
|
|
2639
|
+
if (branch === "review") {
|
|
2640
|
+
return formatOmissionRiskEntries([
|
|
2641
|
+
["security/approval TTLs", missing([
|
|
2642
|
+
"backend/app/services/runtime/codex_approval.py",
|
|
2643
|
+
"backend/app/api/endpoints/permissions.py",
|
|
2644
|
+
"backend/tests/services/runtime/test_codex_approval_bridge.py",
|
|
2645
|
+
])],
|
|
2646
|
+
["runtime-session contamination", missing([
|
|
2647
|
+
"backend/app/services/runtime/codex.py",
|
|
2648
|
+
"backend/app/services/streaming/orchestrator.py",
|
|
2649
|
+
"backend/app/services/runtime_session_service.py",
|
|
2650
|
+
"backend/tests/services/runtime/test_codex_runtime_invariants.py",
|
|
2651
|
+
])],
|
|
2652
|
+
["frontend attachment gating", missing([
|
|
2653
|
+
"frontend/src/components/chat/message-input/Input.tsx",
|
|
2654
|
+
"frontend/src/components/ui/FileUploadDialog.tsx",
|
|
2655
|
+
"frontend/src/components/ui/FileUploadDialog.codex.test.tsx",
|
|
2656
|
+
])],
|
|
2657
|
+
]);
|
|
2658
|
+
}
|
|
2659
|
+
if (branch === "debug") {
|
|
2660
|
+
return formatOmissionRiskEntries([
|
|
2661
|
+
["row identity and virtualizer tests", missing([
|
|
2662
|
+
"frontend/src/components/right-panel/RightPanelDevtools.tsx",
|
|
2663
|
+
"frontend/src/components/right-panel/__tests__/right-panel-devtools.test.tsx",
|
|
2664
|
+
])],
|
|
2665
|
+
["chat cache lookup", missing([
|
|
2666
|
+
"frontend/src/components/chat/chat-window/Chat.tsx",
|
|
2667
|
+
"frontend/src/hooks/queries/queryKeys.ts",
|
|
2668
|
+
"frontend/src/utils/messageCacheOrder.ts",
|
|
2669
|
+
])],
|
|
2670
|
+
["message/tool anchor expansion", missing([
|
|
2671
|
+
"frontend/src/components/chat/message-bubble/MessageRenderer.tsx",
|
|
2672
|
+
"frontend/src/components/chat/tools/common/ToolCard.tsx",
|
|
2673
|
+
"frontend/src/components/chat/tools/common/__tests__/toolExpand.test.tsx",
|
|
2674
|
+
])],
|
|
2675
|
+
]);
|
|
2676
|
+
}
|
|
2677
|
+
return undefined;
|
|
2678
|
+
}
|
|
2679
|
+
function formatOmissionRiskEntries(entries) {
|
|
2680
|
+
const risks = entries.filter((entry) => entry[1].length > 0);
|
|
2681
|
+
if (risks.length === 0) {
|
|
2682
|
+
return undefined;
|
|
2683
|
+
}
|
|
2684
|
+
return `- Omission risk: ${risks
|
|
2685
|
+
.slice(0, 3)
|
|
2686
|
+
.map(([label, paths]) => `${label} needs ${paths.slice(0, 2).join(", ")}`)
|
|
2687
|
+
.join("; ")}.`;
|
|
2688
|
+
}
|
|
2689
|
+
function mentionsAny(input, needles) {
|
|
2690
|
+
return needles.some((needle) => input.includes(needle));
|
|
2691
|
+
}
|
|
2692
|
+
function renderConfirmedReviewSourceAreas(selected, showRootLabels) {
|
|
2693
|
+
const expected = [
|
|
2694
|
+
"backend/app/services/runtime/resolver.py",
|
|
2695
|
+
"backend/app/services/runtime/codex_approval.py",
|
|
2696
|
+
"backend/app/models/schemas/permissions.py",
|
|
2697
|
+
"backend/tests/services/runtime/test_codex_approval_bridge.py",
|
|
2698
|
+
];
|
|
2699
|
+
const selectedPaths = selectedPathSet(selected);
|
|
2700
|
+
const present = expected
|
|
2701
|
+
.filter((path) => selectedPaths.has(path))
|
|
2702
|
+
.map((path) => briefRef(selected, showRootLabels, path));
|
|
2703
|
+
const missing = expected.filter((path) => !selectedPaths.has(path));
|
|
2704
|
+
if (present.length === 0) {
|
|
2705
|
+
return `- Confirmed source areas: none of the preferred review guardrails are selected. Verify before quoting: ${missing.join(", ")}.`;
|
|
2706
|
+
}
|
|
2707
|
+
const suffix = missing.length === 0
|
|
2708
|
+
? ""
|
|
2709
|
+
: ` Additional verification targets: ${missing.join(", ")}.`;
|
|
2710
|
+
return `- Confirmed source areas: selected ranges around ${present.join(", ")} are the guardrails to quote as facts.${suffix}`;
|
|
2711
|
+
}
|
|
2712
|
+
const REVIEW_TASK_KEYWORDS = [
|
|
2713
|
+
"code review",
|
|
2714
|
+
"review",
|
|
2715
|
+
"codex approval",
|
|
2716
|
+
];
|
|
2717
|
+
const DEBUG_TASK_KEYWORDS = [
|
|
2718
|
+
"devtools",
|
|
2719
|
+
"trace row",
|
|
2720
|
+
"row-to-message",
|
|
2721
|
+
"click-through",
|
|
2722
|
+
"highlight",
|
|
2723
|
+
];
|
|
2724
|
+
const RUNTIME_TASK_KEYWORDS = [
|
|
2725
|
+
"runtime settings",
|
|
2726
|
+
"runtime_permission_mode",
|
|
2727
|
+
"runtime_effort_level",
|
|
2728
|
+
"queue dispatch",
|
|
2729
|
+
];
|
|
2730
|
+
function classifyTaskBranch(normalizedTask) {
|
|
2731
|
+
if (mentionsAny(normalizedTask, DEBUG_TASK_KEYWORDS)) {
|
|
2732
|
+
return "debug";
|
|
2733
|
+
}
|
|
2734
|
+
if (mentionsAny(normalizedTask, REVIEW_TASK_KEYWORDS)) {
|
|
2735
|
+
return "review";
|
|
2736
|
+
}
|
|
2737
|
+
if (mentionsAny(normalizedTask, RUNTIME_TASK_KEYWORDS)) {
|
|
2738
|
+
return "runtime";
|
|
2739
|
+
}
|
|
2740
|
+
return "generic";
|
|
2741
|
+
}
|
|
2742
|
+
function selectedPathSet(selected) {
|
|
2743
|
+
return new Set(selected.map((file) => file.path));
|
|
2744
|
+
}
|
|
2745
|
+
function relationshipLine(label, selected, showRootLabels, paths) {
|
|
2746
|
+
const present = paths.filter((path) => selected.has(path));
|
|
2747
|
+
const missing = paths.filter((path) => !selected.has(path));
|
|
2748
|
+
const chain = present.length === 0
|
|
2749
|
+
? "unavailable; no expected anchors selected"
|
|
2750
|
+
: present
|
|
2751
|
+
.slice(0, 8)
|
|
2752
|
+
.map((path) => `${renderRootPrefix(0, showRootLabels)}${path}`)
|
|
2753
|
+
.join(" -> ");
|
|
2754
|
+
return `- ${label} chain: ${chain}.`;
|
|
2755
|
+
}
|
|
2756
|
+
function taskActionCue(task) {
|
|
2757
|
+
const verbs = [
|
|
2758
|
+
"add",
|
|
2759
|
+
"audit",
|
|
2760
|
+
"build",
|
|
2761
|
+
"debug",
|
|
2762
|
+
"diagnose",
|
|
2763
|
+
"explain",
|
|
2764
|
+
"fix",
|
|
2765
|
+
"harden",
|
|
2766
|
+
"implement",
|
|
2767
|
+
"inspect",
|
|
2768
|
+
"investigate",
|
|
2769
|
+
"plan",
|
|
2770
|
+
"refactor",
|
|
2771
|
+
"remove",
|
|
2772
|
+
"review",
|
|
2773
|
+
"summarize",
|
|
2774
|
+
"test",
|
|
2775
|
+
"update",
|
|
2776
|
+
"verify",
|
|
2777
|
+
];
|
|
2778
|
+
const words = new Set(task.toLowerCase().match(/[a-z][a-z0-9_-]*/g) ?? []);
|
|
2779
|
+
return verbs.find((verb) => words.has(verb));
|
|
2780
|
+
}
|
|
2781
|
+
function relationshipCuesFromSelected(selected) {
|
|
2782
|
+
if (selected.length === 0) {
|
|
2783
|
+
return "no selected files";
|
|
2784
|
+
}
|
|
2785
|
+
const sourceCounts = new Map();
|
|
2786
|
+
for (const file of selected) {
|
|
2787
|
+
sourceCounts.set(file.source, (sourceCounts.get(file.source) ?? 0) + 1);
|
|
2788
|
+
}
|
|
2789
|
+
const sources = [...sourceCounts.entries()]
|
|
2790
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
2791
|
+
.map(([source, count]) => `${source}:${count}`)
|
|
2792
|
+
.join(", ");
|
|
2793
|
+
const areas = renderSelectedPathGroups(selected).slice(0, 3).join("; ");
|
|
2794
|
+
return `${sources}${areas.length === 0 ? "" : ` across ${areas}`}`;
|
|
2795
|
+
}
|
|
2796
|
+
function riskCuesFromContext(input) {
|
|
2797
|
+
if (input.materialOmissions.length > 0) {
|
|
2798
|
+
return input.materialOmissions
|
|
2799
|
+
.slice(0, 3)
|
|
2800
|
+
.map((omission) => humanOmissionReason(omission.reason))
|
|
2801
|
+
.join(", ");
|
|
2802
|
+
}
|
|
2803
|
+
const closure = closureFiles(input.selected, input.showRootLabels).slice(0, 3);
|
|
2804
|
+
if (closure.length > 0) {
|
|
2805
|
+
return `contract files selected (${closure.join(", ")})`;
|
|
2806
|
+
}
|
|
2807
|
+
if (input.selectedSearch.length === 0) {
|
|
2808
|
+
return "no selected-file search hits";
|
|
2809
|
+
}
|
|
2810
|
+
return "none recorded";
|
|
2811
|
+
}
|
|
2812
|
+
function renderSelectedPathGroups(selected) {
|
|
2813
|
+
const counts = new Map();
|
|
2814
|
+
for (const file of selected) {
|
|
2815
|
+
const group = pathGroup(file.path);
|
|
2816
|
+
counts.set(group, (counts.get(group) ?? 0) + 1);
|
|
2817
|
+
}
|
|
2818
|
+
return [...counts.entries()]
|
|
2819
|
+
.sort((left, right) => right[1] - left[1] || left[0].localeCompare(right[0]))
|
|
2820
|
+
.slice(0, 5)
|
|
2821
|
+
.map(([group, count]) => `${group} (${count})`);
|
|
2822
|
+
}
|
|
2823
|
+
function pathGroup(path) {
|
|
2824
|
+
const parts = path.split("/");
|
|
2825
|
+
if (path.startsWith("backend/app/") && parts.length >= 4) {
|
|
2826
|
+
return parts.slice(0, 4).join("/");
|
|
2827
|
+
}
|
|
2828
|
+
if (path.startsWith("frontend/src/") && parts.length >= 4) {
|
|
2829
|
+
return parts.slice(0, 4).join("/");
|
|
2830
|
+
}
|
|
2831
|
+
return parts.slice(0, Math.min(parts.length, 2)).join("/");
|
|
2832
|
+
}
|
|
2833
|
+
function closureFiles(selected, showRootLabels) {
|
|
2834
|
+
return selected
|
|
2835
|
+
.filter((file) => isClosurePath(file.path))
|
|
2836
|
+
.slice(0, 8)
|
|
2837
|
+
.map((file) => `${renderRootPrefix(file.root_index, showRootLabels)}${file.path}`);
|
|
2838
|
+
}
|
|
2839
|
+
function isClosurePath(path) {
|
|
2840
|
+
const normalized = path.toLowerCase();
|
|
2841
|
+
const basename = normalized.split("/").at(-1) ?? normalized;
|
|
2842
|
+
return (normalized.includes("/types/") ||
|
|
2843
|
+
normalized.includes("/schemas/") ||
|
|
2844
|
+
normalized.includes("/constants.") ||
|
|
2845
|
+
normalized.includes("/config.") ||
|
|
2846
|
+
normalized.includes("/querykeys.") ||
|
|
2847
|
+
basename.endsWith(".types.ts") ||
|
|
2848
|
+
basename === "types.ts" ||
|
|
2849
|
+
basename === "schema.ts" ||
|
|
2850
|
+
basename === "schemas.ts" ||
|
|
2851
|
+
basename === "constants.ts" ||
|
|
2852
|
+
basename === "constants.py" ||
|
|
2853
|
+
basename === "config.ts" ||
|
|
2854
|
+
basename === "config.py" ||
|
|
2855
|
+
basename === "querykeys.ts" ||
|
|
2856
|
+
basename === "permission_mode.py" ||
|
|
2857
|
+
basename === "effort.py");
|
|
2858
|
+
}
|
|
2859
|
+
function renderRepositoryShape(input) {
|
|
2860
|
+
const { tree, selected, showRootLabels } = input;
|
|
2861
|
+
if (tree.length === 0) {
|
|
2862
|
+
return "- No files discovered.";
|
|
2863
|
+
}
|
|
2864
|
+
const selectedKeys = selectedFileKeys(selected);
|
|
2865
|
+
const selectedTree = tree.filter((file) => selectedKeys.has(fileKey(file)));
|
|
2866
|
+
if (selectedTree.length === 0) {
|
|
2867
|
+
return "- Full repository shape is available in tree.json; no selected files were present in the discovered tree.";
|
|
2868
|
+
}
|
|
2869
|
+
return [
|
|
2870
|
+
"- Full repository shape is available in tree.json; inline shape is limited to selected files.",
|
|
2871
|
+
...selectedTree
|
|
2872
|
+
.map((file) => {
|
|
2873
|
+
const flags = [
|
|
2874
|
+
file.eligible ? "eligible" : "omitted",
|
|
2875
|
+
file.content_eligible ? "content" : "metadata-only",
|
|
2876
|
+
].join(", ");
|
|
2877
|
+
return `- ${renderRootPrefix(file.root_index, showRootLabels)}${file.path} (${file.size_bytes} bytes; ${flags})`;
|
|
2878
|
+
}),
|
|
2879
|
+
].join("\n");
|
|
2880
|
+
}
|
|
2881
|
+
function renderSearchEvidence(input) {
|
|
2882
|
+
const { search, selected, showRootLabels } = input;
|
|
2883
|
+
if (search.length === 0) {
|
|
2884
|
+
return "- No search hits recorded.";
|
|
2885
|
+
}
|
|
2886
|
+
const selectedPathKeys = selectedFileKeys(selected);
|
|
2887
|
+
const selectedHits = search.filter((record) => selectedPathKeys.has(fileKey(record)));
|
|
2888
|
+
const repoWideHits = search.filter((record) => !selectedPathKeys.has(fileKey(record)));
|
|
2889
|
+
const lines = [];
|
|
2890
|
+
lines.push(renderSelectedSearchSummary(selectedHits, showRootLabels));
|
|
2891
|
+
if (repoWideHits.length > 0) {
|
|
2892
|
+
lines.push(`- Repo-wide hits suppressed from inline bundle: ${repoWideHits.length} hits across ${countSearchPaths(repoWideHits)} unselected files; see search.jsonl.`);
|
|
2893
|
+
}
|
|
2894
|
+
return lines.join("\n");
|
|
2895
|
+
}
|
|
2896
|
+
function renderSelectedSearchSummary(selectedHits, showRootLabels) {
|
|
2897
|
+
if (selectedHits.length === 0) {
|
|
2898
|
+
return "- Selected-file hits: none.";
|
|
2899
|
+
}
|
|
2900
|
+
const countByPath = new Map();
|
|
2901
|
+
for (const record of selectedHits) {
|
|
2902
|
+
const path = `${renderRootPrefix(record.root_index, showRootLabels)}${record.path}`;
|
|
2903
|
+
countByPath.set(path, (countByPath.get(path) ?? 0) + 1);
|
|
2904
|
+
}
|
|
2905
|
+
const topPaths = [...countByPath.entries()]
|
|
2906
|
+
.sort((left, right) => right[1] - left[1] || left[0].localeCompare(right[0]))
|
|
2907
|
+
.slice(0, 12)
|
|
2908
|
+
.map(([path, count]) => `${path}=${count}`)
|
|
2909
|
+
.join(", ");
|
|
2910
|
+
const suppressed = countByPath.size > 12 ? `; ${countByPath.size - 12} more selected files in search.jsonl` : "";
|
|
2911
|
+
return `- Selected-file hits: ${selectedHits.length} hits across ${countByPath.size} selected files (${topPaths}${suppressed}).`;
|
|
2912
|
+
}
|
|
2913
|
+
function countSearchPaths(search) {
|
|
2914
|
+
return new Set(search.map(fileKey)).size;
|
|
2915
|
+
}
|
|
2916
|
+
function renderFileContext(selected, search, showRootLabels) {
|
|
2917
|
+
if (selected.length === 0) {
|
|
2918
|
+
return "No file context selected.";
|
|
2919
|
+
}
|
|
2920
|
+
const totalRanges = selected.reduce((total, file) => total + file.ranges.length, 0);
|
|
2921
|
+
const totalBytes = selected.reduce((total, file) => total + file.bytes, 0);
|
|
2922
|
+
const previewFiles = selected.slice(0, 12).map((file) => {
|
|
2923
|
+
const ranges = file.ranges
|
|
2924
|
+
.slice(0, 4)
|
|
2925
|
+
.map((range) => `${range.start_line}-${range.end_line}`)
|
|
2926
|
+
.join(", ");
|
|
2927
|
+
const suffix = file.ranges.length > 4 ? ` +${file.ranges.length - 4}` : "";
|
|
2928
|
+
return `- ${renderRootPrefix(file.root_index, showRootLabels)}${file.path}: ${ranges}${suffix}`;
|
|
2929
|
+
});
|
|
2930
|
+
const hidden = selected.length > previewFiles.length
|
|
2931
|
+
? [`- ${selected.length - previewFiles.length} more selected files are listed in manifest.json and slices.jsonl.`]
|
|
2932
|
+
: [];
|
|
2933
|
+
const selectedHitPreviews = renderSelectedHitPreviews({
|
|
2934
|
+
selected,
|
|
2935
|
+
search,
|
|
2936
|
+
showRootLabels,
|
|
2937
|
+
});
|
|
2938
|
+
const selectedRangeLandmarks = selected.length > 12
|
|
2939
|
+
? []
|
|
2940
|
+
: renderSelectedRangeLandmarks({
|
|
2941
|
+
selected,
|
|
2942
|
+
showRootLabels,
|
|
2943
|
+
});
|
|
2944
|
+
return [
|
|
2945
|
+
"- Full selected source is also stored in `slices.jsonl`; the task-critical evidence is inlined above for bundle-first review.",
|
|
2946
|
+
`- Selected source payload: ${selected.length} files, ${totalRanges} ranges, ${totalBytes} bytes before artifact serialization.`,
|
|
2947
|
+
"- Range index:",
|
|
2948
|
+
...previewFiles,
|
|
2949
|
+
...hidden,
|
|
2950
|
+
...(selectedRangeLandmarks.length === 0
|
|
2951
|
+
? []
|
|
2952
|
+
: ["", "- Range landmarks:", ...selectedRangeLandmarks]),
|
|
2953
|
+
...(selected.length > 12 || selectedHitPreviews.length === 0
|
|
2954
|
+
? []
|
|
2955
|
+
: ["", "- Selected hit previews:", ...selectedHitPreviews]),
|
|
2956
|
+
].join("\n");
|
|
2957
|
+
}
|
|
2958
|
+
function renderSelectedRangeLandmarks(input) {
|
|
2959
|
+
const rendered = [];
|
|
2960
|
+
const seen = new Set();
|
|
2961
|
+
for (const file of input.selected) {
|
|
2962
|
+
let fileCount = 0;
|
|
2963
|
+
const rangeLines = splitSelectedFileContentByRange(file);
|
|
2964
|
+
file.ranges.forEach((range, rangeIndex) => {
|
|
2965
|
+
const lines = rangeLines[rangeIndex] ?? [];
|
|
2966
|
+
let rangeCount = 0;
|
|
2967
|
+
lines.forEach((line, offset) => {
|
|
2968
|
+
const preview = line.trim();
|
|
2969
|
+
if (preview.length === 0 || !isRangeLandmarkLine(preview)) {
|
|
2970
|
+
return;
|
|
2971
|
+
}
|
|
2972
|
+
const lineNumber = range.start_line + offset;
|
|
2973
|
+
const key = `${file.root_index}\u0000${file.path}\u0000${lineNumber}\u0000${preview}`;
|
|
2974
|
+
if (seen.has(key)) {
|
|
2975
|
+
return;
|
|
2976
|
+
}
|
|
2977
|
+
seen.add(key);
|
|
2978
|
+
rendered.push(` - ${renderRootPrefix(file.root_index, input.showRootLabels)}${file.path}:${lineNumber} ${preview}`);
|
|
2979
|
+
fileCount += 1;
|
|
2980
|
+
rangeCount += 1;
|
|
2981
|
+
});
|
|
2982
|
+
});
|
|
2983
|
+
if (fileCount >= 16 || rendered.length >= 48) {
|
|
2984
|
+
break;
|
|
2985
|
+
}
|
|
2986
|
+
}
|
|
2987
|
+
return rendered.slice(0, 48);
|
|
2988
|
+
}
|
|
2989
|
+
function isRangeLandmarkLine(line) {
|
|
2990
|
+
if (symbolsFromLine(line).length > 0) {
|
|
2991
|
+
return true;
|
|
2992
|
+
}
|
|
2993
|
+
return /\b(?:boundary|needle|requestId|originalIndex|write_active_asset)\b/.test(line);
|
|
2994
|
+
}
|
|
2995
|
+
function renderSelectedHitPreviews(input) {
|
|
2996
|
+
const selectedPaths = new Set(input.selected.map((file) => `${file.root_index}\u0000${file.path}`));
|
|
2997
|
+
const rendered = [];
|
|
2998
|
+
const seen = new Set();
|
|
2999
|
+
const perFileCount = new Map();
|
|
3000
|
+
for (const record of input.search) {
|
|
3001
|
+
const fileKey = `${record.root_index}\u0000${record.path}`;
|
|
3002
|
+
if (!selectedPaths.has(fileKey)) {
|
|
3003
|
+
continue;
|
|
3004
|
+
}
|
|
3005
|
+
if ((perFileCount.get(fileKey) ?? 0) >= 4) {
|
|
3006
|
+
continue;
|
|
3007
|
+
}
|
|
3008
|
+
const preview = record.preview.trim();
|
|
3009
|
+
if (preview.length === 0) {
|
|
3010
|
+
continue;
|
|
3011
|
+
}
|
|
3012
|
+
const hitKey = `${fileKey}\u0000${record.line}\u0000${preview}`;
|
|
3013
|
+
if (seen.has(hitKey)) {
|
|
3014
|
+
continue;
|
|
3015
|
+
}
|
|
3016
|
+
seen.add(hitKey);
|
|
3017
|
+
perFileCount.set(fileKey, (perFileCount.get(fileKey) ?? 0) + 1);
|
|
3018
|
+
rendered.push(` - ${renderRootPrefix(record.root_index, input.showRootLabels)}${record.path}:${record.line}:${record.column} matches "${record.search_term}": ${preview}`);
|
|
3019
|
+
if (rendered.length >= 24) {
|
|
3020
|
+
break;
|
|
3021
|
+
}
|
|
3022
|
+
}
|
|
3023
|
+
return rendered;
|
|
3024
|
+
}
|
|
3025
|
+
function renderRootPrefix(rootIndex, showRootLabels) {
|
|
3026
|
+
return showRootLabels ? `root ${rootIndex}: ` : "";
|
|
3027
|
+
}
|
|
3028
|
+
function renderOpenQuestions(questions) {
|
|
3029
|
+
if (questions.length === 0) {
|
|
3030
|
+
return "- None.";
|
|
3031
|
+
}
|
|
3032
|
+
return questions.map((question) => `- ${question}`).join("\n");
|
|
3033
|
+
}
|
|
3034
|
+
function renderOmissionsAndBudgetNotes(input) {
|
|
3035
|
+
const maxMaterialOmissions = 12;
|
|
3036
|
+
const maxPathsPerMaterialOmission = 5;
|
|
3037
|
+
const notes = [];
|
|
3038
|
+
notes.push(`- Metrics: selected ${input.metrics.files_selected} files, recorded ${input.omissions.length} omissions, estimated ${input.metrics.estimated_tokens} tokens.`);
|
|
3039
|
+
if (input.metrics.truncated) {
|
|
3040
|
+
notes.push("- Output was truncated or budget-limited.");
|
|
3041
|
+
}
|
|
3042
|
+
for (const warning of input.warnings.filter(isHumanUsefulWarning)) {
|
|
3043
|
+
notes.push(`- Warning ${warning.code}: ${warning.message}`);
|
|
3044
|
+
}
|
|
3045
|
+
const materialOmissions = input.omissions.filter(isMaterialOmission);
|
|
3046
|
+
const incidentalOmissions = input.omissions.filter((omission) => !isMaterialOmission(omission));
|
|
3047
|
+
if (materialOmissions.length > 0) {
|
|
3048
|
+
notes.push("- Material omissions:");
|
|
3049
|
+
}
|
|
3050
|
+
for (const omission of materialOmissions.slice(0, maxMaterialOmissions)) {
|
|
3051
|
+
notes.push(` - ${humanOmissionReason(omission.reason)}: ${omission.message}${renderOmissionPathSuffix(omission.paths, maxPathsPerMaterialOmission)}`);
|
|
3052
|
+
}
|
|
3053
|
+
if (materialOmissions.length > maxMaterialOmissions) {
|
|
3054
|
+
const hidden = materialOmissions.length - maxMaterialOmissions;
|
|
3055
|
+
const countByReason = summarizeOmissionReasons(materialOmissions.slice(maxMaterialOmissions));
|
|
3056
|
+
notes.push(` - ${hidden} additional material omissions summarized: ${countByReason}. Full details are in manifest.json and summary.json.`);
|
|
3057
|
+
}
|
|
3058
|
+
if (incidentalOmissions.length > 0) {
|
|
3059
|
+
notes.push(`- Incidental omissions summarized: ${summarizeOmissionReasons(incidentalOmissions)}.`);
|
|
3060
|
+
}
|
|
3061
|
+
return notes.join("\n");
|
|
3062
|
+
}
|
|
3063
|
+
function isHumanUsefulWarning(warning) {
|
|
3064
|
+
if (warning.code === "delegate_curator_warning" ||
|
|
3065
|
+
warning.code === "delegate_finalizer_warning") {
|
|
3066
|
+
const normalized = warning.message.toLowerCase();
|
|
3067
|
+
return !(normalized.includes("no filesystem inspection") ||
|
|
3068
|
+
normalized.includes("no filesystem discovery") ||
|
|
3069
|
+
normalized.includes("budget-pruned") ||
|
|
3070
|
+
normalized.includes("packet-provided") ||
|
|
3071
|
+
normalized.includes("packet candidates") ||
|
|
3072
|
+
normalized.includes("packet.candidates") ||
|
|
3073
|
+
normalized.includes("candidate ids and ranges") ||
|
|
3074
|
+
normalized.includes("packet budget") ||
|
|
3075
|
+
normalized.includes("packet ranges") ||
|
|
3076
|
+
normalized.includes("packet did not provide complete source") ||
|
|
3077
|
+
normalized.includes("partial because the packet") ||
|
|
3078
|
+
normalized.includes("empty excerpts") ||
|
|
3079
|
+
normalized.includes("large files were kept") ||
|
|
3080
|
+
normalized.includes("excerpt-only for many slices") ||
|
|
3081
|
+
normalized.includes("packet authority was enforced") ||
|
|
3082
|
+
normalized.includes("curator selected fewer local candidates") ||
|
|
3083
|
+
normalized.includes("server-expanded") ||
|
|
3084
|
+
normalized.includes("server-retained") ||
|
|
3085
|
+
normalized.includes("server-evicted") ||
|
|
3086
|
+
normalized.includes("full ranges were selected") ||
|
|
3087
|
+
normalized.includes("selection intentionally keeps deterministic candidates") ||
|
|
3088
|
+
normalized.includes("full source") ||
|
|
3089
|
+
normalized.includes("full excerpts") ||
|
|
3090
|
+
normalized.includes("expected anchor") ||
|
|
3091
|
+
normalized.includes("intentionally excluded") ||
|
|
3092
|
+
normalized.includes("intentionally excludes") ||
|
|
3093
|
+
normalized.includes("broad search") ||
|
|
3094
|
+
normalized.includes("search spillover") ||
|
|
3095
|
+
normalized.includes("packet slices") ||
|
|
3096
|
+
normalized.includes("short packet ranges") ||
|
|
3097
|
+
normalized.includes("narrow ranges") ||
|
|
3098
|
+
normalized.includes("search-discovered candidates") ||
|
|
3099
|
+
normalized.includes("partial deterministic ranges") ||
|
|
3100
|
+
normalized.includes("sparse for many deterministic ranges") ||
|
|
3101
|
+
normalized.includes("narrow eligible ranges") ||
|
|
3102
|
+
normalized.includes("targeted slices"));
|
|
3103
|
+
}
|
|
3104
|
+
return true;
|
|
3105
|
+
}
|
|
3106
|
+
function renderOmissionPathSuffix(paths, limit) {
|
|
3107
|
+
if (paths == null || paths.length === 0) {
|
|
3108
|
+
return "";
|
|
3109
|
+
}
|
|
3110
|
+
const uniquePaths = [...new Set(paths)];
|
|
3111
|
+
const rendered = uniquePaths.slice(0, limit);
|
|
3112
|
+
const omitted = uniquePaths.length - rendered.length;
|
|
3113
|
+
return ` (${rendered.join(", ")}${omitted > 0 ? ` +${omitted} more` : ""})`;
|
|
3114
|
+
}
|
|
3115
|
+
function humanOmissionReason(reason) {
|
|
3116
|
+
if (reason === "target_estimated_tokens") {
|
|
3117
|
+
return "budget-limited";
|
|
3118
|
+
}
|
|
3119
|
+
if (reason === "max_files") {
|
|
3120
|
+
return "file-count-limited";
|
|
3121
|
+
}
|
|
3122
|
+
if (reason === "max_total_bytes" || reason === "max_bytes_per_file") {
|
|
3123
|
+
return "byte-budget-limited";
|
|
3124
|
+
}
|
|
3125
|
+
return reason;
|
|
3126
|
+
}
|
|
3127
|
+
function summarizeOmissionReasons(omissions) {
|
|
3128
|
+
const countByReason = new Map();
|
|
3129
|
+
for (const omission of omissions) {
|
|
3130
|
+
const reason = humanOmissionReason(omission.reason);
|
|
3131
|
+
countByReason.set(reason, (countByReason.get(reason) ?? 0) + 1);
|
|
3132
|
+
}
|
|
3133
|
+
return [...countByReason.entries()]
|
|
3134
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
3135
|
+
.map(([reason, count]) => `${reason}=${count}`)
|
|
3136
|
+
.join(", ");
|
|
3137
|
+
}
|
|
3138
|
+
function isMaterialOmission(omission) {
|
|
3139
|
+
if (new Set([
|
|
3140
|
+
"binary_file",
|
|
3141
|
+
"content_secret",
|
|
3142
|
+
"exclude_glob",
|
|
3143
|
+
"secret_path",
|
|
3144
|
+
"symlink",
|
|
3145
|
+
]).has(omission.reason)) {
|
|
3146
|
+
return false;
|
|
3147
|
+
}
|
|
3148
|
+
return omissionEventBaseFromReason(omission.reason).material === true;
|
|
3149
|
+
}
|
|
3150
|
+
function reconcileSelectedOmissions(omissions, selected) {
|
|
3151
|
+
if (omissions.length === 0 || selected.length === 0) {
|
|
3152
|
+
return [...omissions];
|
|
3153
|
+
}
|
|
3154
|
+
const selectedKeys = selectedFileKeys(selected);
|
|
3155
|
+
const selectedPaths = new Set(selected.map((file) => file.path));
|
|
3156
|
+
const reconciled = [];
|
|
3157
|
+
for (const omission of omissions) {
|
|
3158
|
+
if (omission.paths == null || omission.paths.length === 0) {
|
|
3159
|
+
reconciled.push(omission);
|
|
3160
|
+
continue;
|
|
3161
|
+
}
|
|
3162
|
+
const paths = omission.paths.filter((path) => {
|
|
3163
|
+
if (omission.root_index == null) {
|
|
3164
|
+
return !selectedPaths.has(path);
|
|
3165
|
+
}
|
|
3166
|
+
return !selectedKeys.has(fileKey({ root_index: omission.root_index, path }));
|
|
3167
|
+
});
|
|
3168
|
+
if (paths.length > 0) {
|
|
3169
|
+
reconciled.push({ ...omission, paths });
|
|
3170
|
+
}
|
|
3171
|
+
}
|
|
3172
|
+
return reconciled;
|
|
3173
|
+
}
|
|
3174
|
+
function filterTaskPresentationOmissions(omissions, task) {
|
|
3175
|
+
const branch = classifyTaskBranch(task.toLowerCase());
|
|
3176
|
+
return omissions
|
|
3177
|
+
.map((omission) => {
|
|
3178
|
+
if (omission.paths == null) {
|
|
3179
|
+
return omission;
|
|
3180
|
+
}
|
|
3181
|
+
const paths = omission.paths.filter((path) => !isTangentialPresentationOmissionPath(path, branch));
|
|
3182
|
+
return { ...omission, paths };
|
|
3183
|
+
})
|
|
3184
|
+
.filter((omission) => {
|
|
3185
|
+
if ((omission.paths?.length ?? 0) > 0) {
|
|
3186
|
+
return true;
|
|
3187
|
+
}
|
|
3188
|
+
return !isMaterialOmission(omission);
|
|
3189
|
+
});
|
|
3190
|
+
}
|
|
3191
|
+
function filterTaskPresentationSelected(selected, task) {
|
|
3192
|
+
const branch = classifyTaskBranch(task.toLowerCase());
|
|
3193
|
+
return selected.filter((file) => !isTangentialPresentationSelectedPath(file.path, branch));
|
|
3194
|
+
}
|
|
3195
|
+
function promoteTaskPresentationEvidence(selected, task, slices) {
|
|
3196
|
+
const branch = classifyTaskBranch(task.toLowerCase());
|
|
3197
|
+
const required = branch === "review"
|
|
3198
|
+
? [
|
|
3199
|
+
"frontend/src/components/chat/message-input/Input.tsx",
|
|
3200
|
+
"frontend/src/components/ui/FileUploadDialog.codex.test.tsx",
|
|
3201
|
+
]
|
|
3202
|
+
: [];
|
|
3203
|
+
if (required.length === 0) {
|
|
3204
|
+
return [...selected];
|
|
3205
|
+
}
|
|
3206
|
+
const selectedPaths = selectedPathSet(selected);
|
|
3207
|
+
const evidenceByPath = bestEvidenceSliceByPath(slices, evidenceTermsForTask(task));
|
|
3208
|
+
const promoted = [];
|
|
3209
|
+
for (const path of required) {
|
|
3210
|
+
if (selectedPaths.has(path)) {
|
|
3211
|
+
continue;
|
|
3212
|
+
}
|
|
3213
|
+
const evidence = evidenceByPath.get(path);
|
|
3214
|
+
if (evidence != null && hasSnippetEligibleContent(evidence)) {
|
|
3215
|
+
promoted.push(evidence);
|
|
3216
|
+
}
|
|
3217
|
+
}
|
|
3218
|
+
if (promoted.length === 0) {
|
|
3219
|
+
return [...selected];
|
|
3220
|
+
}
|
|
3221
|
+
return [...selected, ...promoted];
|
|
3222
|
+
}
|
|
3223
|
+
function isTangentialPresentationSelectedPath(path, branch) {
|
|
3224
|
+
const normalized = path.toLowerCase();
|
|
3225
|
+
if (branch === "runtime") {
|
|
3226
|
+
return normalized.endsWith("/components/chat/chat-window/chat.tsx");
|
|
3227
|
+
}
|
|
3228
|
+
if (branch === "review") {
|
|
3229
|
+
return (normalized.endsWith("/services/sandbox/provider.py") ||
|
|
3230
|
+
normalized.includes("providerauth") ||
|
|
3231
|
+
normalized.includes("provider_auth"));
|
|
3232
|
+
}
|
|
3233
|
+
return false;
|
|
3234
|
+
}
|
|
3235
|
+
function isTangentialPresentationOmissionPath(path, branch) {
|
|
3236
|
+
const normalized = path.toLowerCase();
|
|
3237
|
+
if (branch === "runtime") {
|
|
3238
|
+
return (normalized.includes("/components/settings/") ||
|
|
3239
|
+
normalized.endsWith("/api/endpoints/settings.py") ||
|
|
3240
|
+
normalized.endsWith("/models/schemas/settings.py") ||
|
|
3241
|
+
normalized.includes("/services/nightshift/") ||
|
|
3242
|
+
normalized.includes("/tests/services/nightshift/"));
|
|
3243
|
+
}
|
|
3244
|
+
if (branch === "review") {
|
|
3245
|
+
return (normalized.includes("/services/checks/") ||
|
|
3246
|
+
normalized.endsWith("/api/endpoints/checks.py") ||
|
|
3247
|
+
normalized.endsWith("/models/schemas/checks.py") ||
|
|
3248
|
+
normalized.includes("/tests/checks/") ||
|
|
3249
|
+
normalized.includes("/components/right-panel/checks/"));
|
|
3250
|
+
}
|
|
3251
|
+
return false;
|
|
3252
|
+
}
|
|
3253
|
+
function reconcileSelectedOpenQuestions(questions, selected) {
|
|
3254
|
+
if (questions.length === 0 || selected.length === 0) {
|
|
3255
|
+
return [...questions];
|
|
3256
|
+
}
|
|
3257
|
+
const refs = selectedQuestionReferences(selected);
|
|
3258
|
+
return questions.filter((question) => {
|
|
3259
|
+
if (isConceptualQuestionAnsweredBySelectedContext(question, selected)) {
|
|
3260
|
+
return false;
|
|
3261
|
+
}
|
|
3262
|
+
if (!isSelectedEvidenceQuestion(question)) {
|
|
3263
|
+
return true;
|
|
3264
|
+
}
|
|
3265
|
+
return !questionReferencesSelectedContext(question, refs);
|
|
3266
|
+
});
|
|
3267
|
+
}
|
|
3268
|
+
function buildPresentationOpenQuestions(questions, selected, omissions) {
|
|
3269
|
+
const reconciled = reconcileSelectedOpenQuestions(questions, selected);
|
|
3270
|
+
const seen = new Set(reconciled);
|
|
3271
|
+
const output = [...reconciled];
|
|
3272
|
+
for (const question of omissionFollowUpQuestions(omissions)) {
|
|
3273
|
+
if (seen.has(question)) {
|
|
3274
|
+
continue;
|
|
3275
|
+
}
|
|
3276
|
+
seen.add(question);
|
|
3277
|
+
output.push(question);
|
|
3278
|
+
if (output.length >= 20) {
|
|
3279
|
+
break;
|
|
3280
|
+
}
|
|
3281
|
+
}
|
|
3282
|
+
return output;
|
|
3283
|
+
}
|
|
3284
|
+
function omissionFollowUpQuestions(omissions) {
|
|
3285
|
+
return omissions
|
|
3286
|
+
.filter(isMaterialOmission)
|
|
3287
|
+
.slice(0, 3)
|
|
3288
|
+
.map((omission) => {
|
|
3289
|
+
const reason = humanOmissionReason(omission.reason);
|
|
3290
|
+
const paths = renderOmissionQuestionPaths(omission.paths);
|
|
3291
|
+
if (paths.length === 0) {
|
|
3292
|
+
return `What task-relevant evidence is missing from the ${reason} omission: ${omission.message}?`;
|
|
3293
|
+
}
|
|
3294
|
+
return `Can the omitted ${reason} paths (${paths}) change the answer, risk assessment, or required tests for this task?`;
|
|
3295
|
+
});
|
|
3296
|
+
}
|
|
3297
|
+
function renderOmissionQuestionPaths(paths) {
|
|
3298
|
+
if (paths == null || paths.length === 0) {
|
|
3299
|
+
return "";
|
|
3300
|
+
}
|
|
3301
|
+
const uniquePaths = [...new Set(paths)];
|
|
3302
|
+
const rendered = uniquePaths.slice(0, 3);
|
|
3303
|
+
const omitted = uniquePaths.length - rendered.length;
|
|
3304
|
+
return `${rendered.join(", ")}${omitted > 0 ? ` +${omitted} more` : ""}`;
|
|
3305
|
+
}
|
|
3306
|
+
function isConceptualQuestionAnsweredBySelectedContext(question, selected) {
|
|
3307
|
+
const normalized = question.toLowerCase();
|
|
3308
|
+
const paths = selectedPathSet(selected);
|
|
3309
|
+
if (mentionsAny(normalized, [
|
|
3310
|
+
"filtered trace",
|
|
3311
|
+
"filtered index",
|
|
3312
|
+
"filtered indices",
|
|
3313
|
+
"original index",
|
|
3314
|
+
"original trace indices",
|
|
3315
|
+
"entry id",
|
|
3316
|
+
"virtualizer key",
|
|
3317
|
+
"click target",
|
|
3318
|
+
]) &&
|
|
3319
|
+
paths.has("frontend/src/components/right-panel/RightPanelDevtools.tsx") &&
|
|
3320
|
+
paths.has("frontend/src/store/devtoolsNavStore.ts") &&
|
|
3321
|
+
paths.has("frontend/src/components/chat/chat-window/Chat.tsx")) {
|
|
3322
|
+
return true;
|
|
3323
|
+
}
|
|
3324
|
+
if (mentionsAny(normalized, [
|
|
3325
|
+
"cached message lookup",
|
|
3326
|
+
"cached messages",
|
|
3327
|
+
"older paginated messages",
|
|
3328
|
+
"wrong rendered index",
|
|
3329
|
+
"virtualized scrolling",
|
|
3330
|
+
"disambiguate by chatid",
|
|
3331
|
+
"messages not currently loaded",
|
|
3332
|
+
"infinite-query cache",
|
|
3333
|
+
]) &&
|
|
3334
|
+
paths.has("frontend/src/components/chat/chat-window/Chat.tsx") &&
|
|
3335
|
+
(paths.has("frontend/src/utils/messageCacheOrder.ts") ||
|
|
3336
|
+
paths.has("frontend/src/hooks/queries/queryKeys.ts"))) {
|
|
3337
|
+
return true;
|
|
3338
|
+
}
|
|
3339
|
+
if (mentionsAny(normalized, ["tool-result", "tool result", "tool-use", "tool use"]) &&
|
|
3340
|
+
paths.has("frontend/src/components/chat/message-bubble/MessageRenderer.tsx") &&
|
|
3341
|
+
paths.has("frontend/src/components/chat/tools/common/ToolCard.tsx")) {
|
|
3342
|
+
return true;
|
|
3343
|
+
}
|
|
3344
|
+
if (mentionsAny(normalized, ["highlight", "scroll effects", "repeated clicks"]) &&
|
|
3345
|
+
paths.has("frontend/src/store/devtoolsNavStore.ts") &&
|
|
3346
|
+
paths.has("frontend/src/components/chat/chat-window/Chat.tsx")) {
|
|
3347
|
+
return true;
|
|
3348
|
+
}
|
|
3349
|
+
if (mentionsAny(normalized, [
|
|
3350
|
+
"queued messages",
|
|
3351
|
+
"frontend field names",
|
|
3352
|
+
"runtime_permission_mode",
|
|
3353
|
+
"runtime_effort_level",
|
|
3354
|
+
]) &&
|
|
3355
|
+
paths.has("frontend/src/services/chatService.ts") &&
|
|
3356
|
+
paths.has("backend/app/services/chat.py")) {
|
|
3357
|
+
return true;
|
|
3358
|
+
}
|
|
3359
|
+
if (mentionsAny(normalized, [
|
|
3360
|
+
"effort settings",
|
|
3361
|
+
"effort_level",
|
|
3362
|
+
"codex/openai",
|
|
3363
|
+
"claude/openrouter",
|
|
3364
|
+
"non-codex providers",
|
|
3365
|
+
]) &&
|
|
3366
|
+
paths.has("backend/app/services/runtime/codex.py") &&
|
|
3367
|
+
paths.has("backend/app/services/runtime/claude.py")) {
|
|
3368
|
+
return true;
|
|
3369
|
+
}
|
|
3370
|
+
if (mentionsAny(normalized, [
|
|
3371
|
+
"codex trace",
|
|
3372
|
+
"archived claude",
|
|
3373
|
+
"message_id/tool_use_id",
|
|
3374
|
+
"failed clicks",
|
|
3375
|
+
"assistant messages",
|
|
3376
|
+
"tool_use",
|
|
3377
|
+
"tool_result",
|
|
3378
|
+
"virtualizer keys",
|
|
3379
|
+
"visible indices",
|
|
3380
|
+
"original trace entry ids",
|
|
3381
|
+
]) &&
|
|
3382
|
+
paths.has("frontend/src/components/right-panel/RightPanelDevtools.tsx") &&
|
|
3383
|
+
paths.has("backend/app/api/endpoints/devtools.py") &&
|
|
3384
|
+
paths.has("backend/app/services/session_trace_archive.py")) {
|
|
3385
|
+
return true;
|
|
3386
|
+
}
|
|
3387
|
+
return false;
|
|
3388
|
+
}
|
|
3389
|
+
function isSelectedEvidenceQuestion(question) {
|
|
3390
|
+
return /\b(?:missing|missed|omitted|not selected|not present|unavailable|outside packet|need|needs|verify|confirm|include|includes|contain|contains|selected|available|additional|represented|constraints?|excerpt)\b/i.test(question);
|
|
3391
|
+
}
|
|
3392
|
+
function selectedQuestionReferences(selected) {
|
|
3393
|
+
const refs = new Set();
|
|
3394
|
+
for (const file of selected) {
|
|
3395
|
+
for (const value of selectedQuestionReferenceValues(file.path)) {
|
|
3396
|
+
const normalized = normalizeQuestionReference(value);
|
|
3397
|
+
if (isConcreteQuestionReference(normalized)) {
|
|
3398
|
+
refs.add(normalized);
|
|
3399
|
+
}
|
|
3400
|
+
}
|
|
3401
|
+
}
|
|
3402
|
+
return refs;
|
|
3403
|
+
}
|
|
3404
|
+
function selectedQuestionReferenceValues(path) {
|
|
3405
|
+
const parts = path.split("/");
|
|
3406
|
+
const basename = parts.at(-1) ?? path;
|
|
3407
|
+
const stem = basename.split(".")[0] ?? basename;
|
|
3408
|
+
return [path, basename, stem];
|
|
3409
|
+
}
|
|
3410
|
+
function questionReferencesSelectedContext(question, refs) {
|
|
3411
|
+
const normalized = normalizeQuestionReference(question);
|
|
3412
|
+
for (const ref of refs) {
|
|
3413
|
+
if (normalized.includes(ref)) {
|
|
3414
|
+
return true;
|
|
3415
|
+
}
|
|
3416
|
+
}
|
|
3417
|
+
return false;
|
|
3418
|
+
}
|
|
3419
|
+
function normalizeQuestionReference(value) {
|
|
3420
|
+
return value.toLowerCase().replace(/[^a-z0-9]+/g, "");
|
|
3421
|
+
}
|
|
3422
|
+
function isConcreteQuestionReference(value) {
|
|
3423
|
+
if (value.length < 4) {
|
|
3424
|
+
return false;
|
|
3425
|
+
}
|
|
3426
|
+
return !new Set([
|
|
3427
|
+
"backend",
|
|
3428
|
+
"frontend",
|
|
3429
|
+
"schema",
|
|
3430
|
+
"schemas",
|
|
3431
|
+
"service",
|
|
3432
|
+
"services",
|
|
3433
|
+
"source",
|
|
3434
|
+
"test",
|
|
3435
|
+
"tests",
|
|
3436
|
+
"types",
|
|
3437
|
+
]).has(value);
|
|
3438
|
+
}
|
|
3439
|
+
function selectedFileKeys(selected) {
|
|
3440
|
+
return new Set(selected.map(fileKey));
|
|
3441
|
+
}
|
|
3442
|
+
function fileKey(file) {
|
|
3443
|
+
return `${file.root_index}\u0000${file.path}`;
|
|
3444
|
+
}
|
|
3445
|
+
function boundOpenQuestions(questions) {
|
|
3446
|
+
return questions.slice(0, 20).map((question) => sanitizeDiagnosticText(typeof question === "string" ? question : question.question));
|
|
3447
|
+
}
|
|
3448
|
+
function omissionEventFromDiagnostic(omission) {
|
|
3449
|
+
const base = omissionEventBaseFromReason(omission.reason);
|
|
3450
|
+
const rootIndex = omission.root_index;
|
|
3451
|
+
return {
|
|
3452
|
+
...base,
|
|
3453
|
+
...(rootIndex !== undefined && Number.isInteger(rootIndex) && rootIndex >= 0
|
|
3454
|
+
? { root_index: rootIndex }
|
|
3455
|
+
: {}),
|
|
3456
|
+
...(omission.paths == null || omission.paths.length === 0
|
|
3457
|
+
? {}
|
|
3458
|
+
: { paths: [...omission.paths] }),
|
|
3459
|
+
};
|
|
3460
|
+
}
|
|
3461
|
+
function warningEventFromDiagnostic(warning) {
|
|
3462
|
+
return {
|
|
3463
|
+
reason: "non_material_notice",
|
|
3464
|
+
material: false,
|
|
3465
|
+
code: warning.code,
|
|
3466
|
+
message: warning.message,
|
|
3467
|
+
};
|
|
3468
|
+
}
|
|
3469
|
+
function omissionEventBaseFromReason(reason) {
|
|
3470
|
+
if (reason === "excluded" ||
|
|
3471
|
+
reason === "exclude_glob" ||
|
|
3472
|
+
reason === "caller_include_glob" ||
|
|
3473
|
+
reason === "symlink_omitted" ||
|
|
3474
|
+
reason === "symlink_file" ||
|
|
3475
|
+
reason === "binary_file" ||
|
|
3476
|
+
reason === "duplicate_root") {
|
|
3477
|
+
return { reason: "incidental_skip", material: false };
|
|
3478
|
+
}
|
|
3479
|
+
if (reason === "noisy_file" ||
|
|
3480
|
+
reason === "default_exclude" ||
|
|
3481
|
+
reason === "default_noisy_exclude") {
|
|
3482
|
+
return { reason: "default_noisy_exclude", material: false };
|
|
3483
|
+
}
|
|
3484
|
+
if (reason.includes("secret")) {
|
|
3485
|
+
return { reason: "secret_skip", material: true };
|
|
3486
|
+
}
|
|
3487
|
+
if (reason.includes("unreadable")) {
|
|
3488
|
+
return { reason: "unreadable_file", material: true };
|
|
3489
|
+
}
|
|
3490
|
+
if (reason.includes("max_scan_files") ||
|
|
3491
|
+
reason.includes("max_discovery_bytes") ||
|
|
3492
|
+
reason.includes("max_candidate") ||
|
|
3493
|
+
reason.includes("discovery_byte_cap") ||
|
|
3494
|
+
reason.includes("candidate_cap") ||
|
|
3495
|
+
reason.includes("search_cap") ||
|
|
3496
|
+
reason.includes("scan_cap")) {
|
|
3497
|
+
return { reason: "cap_omission", material: true };
|
|
3498
|
+
}
|
|
3499
|
+
if (reason.startsWith("max_") ||
|
|
3500
|
+
reason.includes("budget") ||
|
|
3501
|
+
reason === "target_estimated_tokens" ||
|
|
3502
|
+
reason === "file_too_large") {
|
|
3503
|
+
return { reason: "budget_omission", material: true };
|
|
3504
|
+
}
|
|
3505
|
+
return { reason: "requested_evidence_omission", material: true };
|
|
3506
|
+
}
|
|
3507
|
+
function boundDebugDiagnostics(diagnostics, caps) {
|
|
3508
|
+
if (diagnostics == null) {
|
|
3509
|
+
return {};
|
|
3510
|
+
}
|
|
3511
|
+
return {
|
|
3512
|
+
...(diagnostics.notes == null
|
|
3513
|
+
? {}
|
|
3514
|
+
: {
|
|
3515
|
+
notes: diagnostics.notes
|
|
3516
|
+
.slice(0, caps.max_warnings)
|
|
3517
|
+
.map(sanitizeDiagnosticText),
|
|
3518
|
+
}),
|
|
3519
|
+
...(diagnostics.counters == null
|
|
3520
|
+
? {}
|
|
3521
|
+
: { counters: sanitizeCounters(diagnostics.counters) }),
|
|
3522
|
+
};
|
|
3523
|
+
}
|
|
3524
|
+
function sanitizeCounters(counters) {
|
|
3525
|
+
return Object.fromEntries(Object.entries(counters)
|
|
3526
|
+
.slice(0, 50)
|
|
3527
|
+
.map(([key, value]) => [
|
|
3528
|
+
sanitizeDiagnosticText(key),
|
|
3529
|
+
typeof value === "string" ? sanitizeDiagnosticText(value) : value,
|
|
3530
|
+
]));
|
|
3531
|
+
}
|
|
3532
|
+
function renderJsonl(records) {
|
|
3533
|
+
return records.map((record) => JSON.stringify(record)).join("\n")
|
|
3534
|
+
+ (records.length === 0 ? "" : "\n");
|
|
3535
|
+
}
|
|
3536
|
+
function validateTaskForRendering(task) {
|
|
3537
|
+
assertNoHighConfidenceSecretsInTask(task);
|
|
3538
|
+
return task;
|
|
3539
|
+
}
|
|
3540
|
+
function safePath(path) {
|
|
3541
|
+
return boundContextBuilderPathList([path], 1)[0] ?? "[redacted-sensitive-path]";
|
|
3542
|
+
}
|
|
3543
|
+
function safeContent(content) {
|
|
3544
|
+
return hasHighConfidenceSecret(content) ? "[redacted-sensitive-content]" : content;
|
|
3545
|
+
}
|
|
3546
|
+
function sanitizeJsonValue(value) {
|
|
3547
|
+
if (typeof value === "string") {
|
|
3548
|
+
const pathSafe = safePath(value);
|
|
3549
|
+
return pathSafe === "[redacted-sensitive-path]"
|
|
3550
|
+
? pathSafe
|
|
3551
|
+
: sanitizeDiagnosticText(value);
|
|
3552
|
+
}
|
|
3553
|
+
if (typeof value === "number" || typeof value === "boolean" || value == null) {
|
|
3554
|
+
return value;
|
|
3555
|
+
}
|
|
3556
|
+
if (Array.isArray(value)) {
|
|
3557
|
+
return value.slice(0, 200).map(sanitizeJsonValue);
|
|
3558
|
+
}
|
|
3559
|
+
if (typeof value === "object") {
|
|
3560
|
+
return Object.fromEntries(Object.entries(value)
|
|
3561
|
+
.slice(0, 200)
|
|
3562
|
+
.map(([key, entryValue]) => [
|
|
3563
|
+
sanitizeDiagnosticText(key),
|
|
3564
|
+
sanitizeJsonValue(entryValue),
|
|
3565
|
+
]));
|
|
3566
|
+
}
|
|
3567
|
+
return String(value);
|
|
3568
|
+
}
|
|
3569
|
+
function markdownFenceFor(content) {
|
|
3570
|
+
const longest = Math.max(3, ...Array.from(content.matchAll(/`+/g), (match) => match[0].length + 1));
|
|
3571
|
+
return "`".repeat(longest);
|
|
3572
|
+
}
|
|
3573
|
+
//# sourceMappingURL=bundle.js.map
|