Skip to content

Commit 8753cf0

Browse files
authored
Merge pull request #2621 from esokullu/main
Fix runtime trace config attribution for unlimited and tabless runs
2 parents 0329858 + f8090ce commit 8753cf0

5 files changed

Lines changed: 107 additions & 33 deletions

File tree

src/chrome/src/agent/agent.js

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -761,29 +761,38 @@ export class Agent extends LoopDetector {
761761
return this.conversationIds.get(tabId) || null;
762762
}
763763

764+
/**
765+
* Snapshot the effective runtime settings for a run. Anything we cannot
766+
* observe is omitted rather than guessed: an absent field reads as "unknown"
767+
* in a dump, while a hard `false`/'ask' would assert a setting the run never
768+
* actually had — exactly the misattribution this metadata exists to prevent.
769+
*/
764770
_runtimeTraceConfig(provider, { tabId = null, mode = null } = {}) {
765771
let extensionVersion = '';
766-
let promptTier = 'full';
767772
try { extensionVersion = chrome.runtime.getManifest().version || ''; } catch {}
768-
try { promptTier = provider?.promptTier || 'full'; } catch {}
769-
const effectiveMode = mode
770-
|| (tabId != null ? this._effectiveRunMode(tabId) : 'ask');
773+
const effectiveMode = mode || (tabId != null ? this._effectiveRunMode(tabId) : null);
771774
return normalizeRuntimeTraceConfig({
772775
extension_version: extensionVersion,
773776
browser_target: 'chrome',
774-
mode: effectiveMode,
775-
prompt_tier: promptTier,
777+
...(effectiveMode ? { mode: effectiveMode } : {}),
778+
prompt_tier: this._resolvePromptTier(provider),
776779
screenshot_redaction: this.screenshotRedaction === true,
777780
strict_secret_mode: this.strictSecretMode === true,
778781
plan_before_act_mode: this._normalizePlanBeforeActMode(this.planBeforeActMode),
779782
auto_screenshot: this.autoScreenshot,
780783
use_site_adapters: this.useSiteAdapters === true,
781784
web_mcp_enabled: this.webMcpEnabled === true,
782-
api_mutations_allowed: tabId != null && this.apiAllowedTabs.has(tabId),
783785
user_memory_enabled: this.userMemoryEnabled === true,
784-
selection_grounded: tabId != null && this.selectionGroundingScopes.has(tabId),
786+
// Per-tab authorizations only mean something in a tab's context.
787+
...(tabId != null ? {
788+
api_mutations_allowed: this.apiAllowedTabs.has(tabId),
789+
selection_grounded: this.selectionGroundingScopes.has(tabId),
790+
} : {}),
785791
image_detail: this.imageDetail,
786-
max_agent_steps: this.maxSteps,
792+
// The steps slider stores 0 for "unlimited", which the agent hydrates as
793+
// Infinity. Round-trip that back to 0 so an unlimited run records as
794+
// unlimited instead of being dropped as a non-integer.
795+
max_agent_steps: Number.isFinite(this.maxSteps) ? this.maxSteps : 0,
787796
max_image_dimension: this.maxImageDimension,
788797
max_screenshots_per_turn: this.maxScreenshotsPerTurn,
789798
});
@@ -9357,14 +9366,15 @@ Rules: no prose intro, no conclusion, no "this screenshot shows...", no layout d
93579366
}
93589367

93599368
/**
9360-
* Resolve the active provider's prompt tier ('compact' | 'mid' | 'full').
9361-
* The provider getter already forces 'full' for cloud providers and applies
9362-
* the per-category defaults (local → 'mid'); we just guard the case where
9363-
* no provider is ready yet (fall back to the full prompt).
9369+
* Resolve a provider's prompt tier ('compact' | 'mid' | 'full'), defaulting
9370+
* to the active provider. The provider getter already forces 'full' for
9371+
* cloud providers and applies the per-category defaults (local → 'mid'); we
9372+
* just guard the case where no provider is ready yet (fall back to the full
9373+
* prompt).
93649374
*/
9365-
_resolvePromptTier() {
9375+
_resolvePromptTier(provider = null) {
93669376
try {
9367-
return this.providerManager.getActive().promptTier || 'full';
9377+
return (provider || this.providerManager.getActive()).promptTier || 'full';
93689378
} catch { return 'full'; }
93699379
}
93709380

src/chrome/src/trace/runtime-config.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,13 @@ const BOOLEAN_FIELDS = Object.freeze([
1717
'selection_grounded',
1818
]);
1919

20+
// Bounds keep the payload sane, not to re-validate settings: each range is a
21+
// superset of what the agent's own normalizers can hold (steps ≤ 200 with 0 =
22+
// unlimited, dimension ≤ 2048, screenshots ≤ 5), so a legitimate setting is
23+
// never silently dropped for being out of range.
2024
const INTEGER_RANGES = Object.freeze({
2125
max_agent_steps: [0, 10_000],
22-
max_image_dimension: [256, 16_384],
26+
max_image_dimension: [1, 16_384],
2327
max_screenshots_per_turn: [0, 1_000],
2428
});
2529

src/firefox/src/agent/agent.js

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -860,29 +860,38 @@ export class Agent extends LoopDetector {
860860
return this.conversationIds.get(tabId) || null;
861861
}
862862

863+
/**
864+
* Snapshot the effective runtime settings for a run. Anything we cannot
865+
* observe is omitted rather than guessed: an absent field reads as "unknown"
866+
* in a dump, while a hard `false`/'ask' would assert a setting the run never
867+
* actually had — exactly the misattribution this metadata exists to prevent.
868+
*/
863869
_runtimeTraceConfig(provider, { tabId = null, mode = null } = {}) {
864870
let extensionVersion = '';
865-
let promptTier = 'full';
866871
try { extensionVersion = browser.runtime.getManifest().version || ''; } catch {}
867-
try { promptTier = provider?.promptTier || 'full'; } catch {}
868-
const effectiveMode = mode
869-
|| (tabId != null ? this._effectiveRunMode(tabId) : 'ask');
872+
const effectiveMode = mode || (tabId != null ? this._effectiveRunMode(tabId) : null);
870873
return normalizeRuntimeTraceConfig({
871874
extension_version: extensionVersion,
872875
browser_target: 'firefox',
873-
mode: effectiveMode,
874-
prompt_tier: promptTier,
876+
...(effectiveMode ? { mode: effectiveMode } : {}),
877+
prompt_tier: this._resolvePromptTier(provider),
875878
screenshot_redaction: this.screenshotRedaction === true,
876879
strict_secret_mode: this.strictSecretMode === true,
877880
plan_before_act_mode: this._normalizePlanBeforeActMode(this.planBeforeActMode),
878881
auto_screenshot: this.autoScreenshot,
879882
use_site_adapters: this.useSiteAdapters === true,
880883
web_mcp_enabled: this.webMcpEnabled === true,
881-
api_mutations_allowed: tabId != null && this.apiAllowedTabs.has(tabId),
882884
user_memory_enabled: this.userMemoryEnabled === true,
883-
selection_grounded: tabId != null && this.selectionGroundingScopes.has(tabId),
885+
// Per-tab authorizations only mean something in a tab's context.
886+
...(tabId != null ? {
887+
api_mutations_allowed: this.apiAllowedTabs.has(tabId),
888+
selection_grounded: this.selectionGroundingScopes.has(tabId),
889+
} : {}),
884890
image_detail: this.imageDetail,
885-
max_agent_steps: this.maxSteps,
891+
// The steps slider stores 0 for "unlimited", which the agent hydrates as
892+
// Infinity. Round-trip that back to 0 so an unlimited run records as
893+
// unlimited instead of being dropped as a non-integer.
894+
max_agent_steps: Number.isFinite(this.maxSteps) ? this.maxSteps : 0,
886895
max_image_dimension: this.maxImageDimension,
887896
max_screenshots_per_turn: this.maxScreenshotsPerTurn,
888897
});
@@ -8152,14 +8161,15 @@ Rules: no prose intro, no conclusion, no "this screenshot shows...", no layout d
81528161
}
81538162

81548163
/**
8155-
* Resolve the active provider's prompt tier ('compact' | 'mid' | 'full').
8156-
* The provider getter already forces 'full' for cloud providers and applies
8157-
* the per-category defaults (local → 'mid'); we just guard the case where
8158-
* no provider is ready yet (fall back to the full prompt).
8164+
* Resolve a provider's prompt tier ('compact' | 'mid' | 'full'), defaulting
8165+
* to the active provider. The provider getter already forces 'full' for
8166+
* cloud providers and applies the per-category defaults (local → 'mid'); we
8167+
* just guard the case where no provider is ready yet (fall back to the full
8168+
* prompt).
81598169
*/
8160-
_resolvePromptTier() {
8170+
_resolvePromptTier(provider = null) {
81618171
try {
8162-
return this.providerManager.getActive().promptTier || 'full';
8172+
return (provider || this.providerManager.getActive()).promptTier || 'full';
81638173
} catch { return 'full'; }
81648174
}
81658175

src/firefox/src/trace/runtime-config.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,13 @@ const BOOLEAN_FIELDS = Object.freeze([
1717
'selection_grounded',
1818
]);
1919

20+
// Bounds keep the payload sane, not to re-validate settings: each range is a
21+
// superset of what the agent's own normalizers can hold (steps ≤ 200 with 0 =
22+
// unlimited, dimension ≤ 2048, screenshots ≤ 5), so a legitimate setting is
23+
// never silently dropped for being out of range.
2024
const INTEGER_RANGES = Object.freeze({
2125
max_agent_steps: [0, 10_000],
22-
max_image_dimension: [256, 16_384],
26+
max_image_dimension: [1, 16_384],
2327
max_screenshots_per_turn: [0, 1_000],
2428
});
2529

test/run.js

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4177,12 +4177,22 @@ test('trace record and JSON exports carry WebBrain version metadata', () => {
41774177
assert.match(recorder, /runtimeConfig: normalizeRuntimeTraceConfig\(meta\.runtimeConfig\)/, `${label}: run record should retain only allowlisted runtime settings`);
41784178
assert.match(agent, new RegExp(`webbrainVersion: ${runtimeName}\\.runtime\\.getManifest\\(\\)\\.version`), `${label}: trace start should read the runtime manifest`);
41794179
assert.match(agent, /runtimeConfig: this\._runtimeTraceConfig\(provider, \{ tabId, mode \}\)/, `${label}: trace start should snapshot effective runtime settings`);
4180+
assert.match(
4181+
agent,
4182+
/runtimeConfig: this\._runtimeTraceConfig\(this\.providerManager\?\.getActive\?\.\(\), \{\s*tabId,\s*mode: 'act',\s*\}\)/,
4183+
`${label}: workflow runs should snapshot effective runtime settings too`,
4184+
);
41804185
assert.match(traceUi, new RegExp(`exportedByWebBrainVersion: ${runtimeName}\\.runtime\\.getManifest\\(\\)\\.version`), `${label}: JSON export should identify the exporting build`);
41814186
assert.match(traceUi, /schema: 'webbrain-trace\/1'/, `${label}: additive version metadata should retain the v1 schema`);
41824187
}
41834188
});
41844189

41854190
test('runtime trace config is versioned, bounded, and secret-free in both browsers', () => {
4191+
assert.equal(
4192+
fs.readFileSync(path.join(ROOT, 'src/chrome/src/trace/runtime-config.js'), 'utf8'),
4193+
fs.readFileSync(path.join(ROOT, 'src/firefox/src/trace/runtime-config.js'), 'utf8'),
4194+
'chrome and firefox runtime trace config modules must remain byte-identical',
4195+
);
41864196
const candidate = {
41874197
schema_version: 99,
41884198
extension_version: '24.7.0-beta.1',
@@ -4234,10 +4244,29 @@ test('runtime trace config is versioned, bounded, and secret-free in both browse
42344244
browser_target: 'safari',
42354245
mode: 'admin',
42364246
max_agent_steps: Infinity,
4237-
max_image_dimension: 42,
4247+
max_image_dimension: 1568.5,
4248+
max_screenshots_per_turn: -1,
42384249
screenshot_redaction: 'yes',
42394250
});
42404251
assert.deepEqual(rejected, { schema_version: 1 });
4252+
4253+
// Integer ranges exist to bound the payload, not to re-validate settings:
4254+
// every value the agent's own normalizers can produce must survive, or the
4255+
// dump silently loses the setting it was added to attribute.
4256+
for (const [field, value] of [
4257+
['max_agent_steps', 0],
4258+
['max_agent_steps', 200],
4259+
['max_image_dimension', 1],
4260+
['max_image_dimension', 2048],
4261+
['max_screenshots_per_turn', 0],
4262+
['max_screenshots_per_turn', 5],
4263+
]) {
4264+
assert.equal(
4265+
RuntimeTraceConfigCh.normalizeRuntimeTraceConfig({ [field]: value })[field],
4266+
value,
4267+
`${field}=${value} is producible by the agent and must not be dropped`,
4268+
);
4269+
}
42414270
});
42424271

42434272
test('trace recorders normalize done only from explicit loop error evidence', () => {
@@ -32472,6 +32501,23 @@ test('WebBrain Cloud groups every generation in a stable conversation session wi
3247232501
assert.equal(main.webbrainRuntimeConfig?.strict_secret_mode, true, `${label}: strict secret setting missing`);
3247332502
assert.equal(main.webbrainRuntimeConfig?.api_mutations_allowed, true, `${label}: per-tab API authorization missing`);
3247432503
assert.ok(!JSON.stringify(main.webbrainRuntimeConfig).includes('apiKey'), `${label}: runtime metadata must remain an allowlist`);
32504+
assert.equal(main.webbrainRuntimeConfig?.max_agent_steps, agent.maxSteps, `${label}: step budget missing`);
32505+
32506+
// "Unlimited" steps hydrate as Infinity; record the stored 0 sentinel so an
32507+
// unlimited run is attributable instead of missing the field entirely.
32508+
const previousMaxSteps = agent.maxSteps;
32509+
agent.maxSteps = Infinity;
32510+
const unlimited = agent._cloudGenerationOptions(cloud, {}, { tabId, generationName: 'main' });
32511+
assert.equal(unlimited.webbrainRuntimeConfig?.max_agent_steps, 0, `${label}: unlimited step budget should record as 0`);
32512+
agent.maxSteps = previousMaxSteps;
32513+
32514+
// Without a tab there is no observable mode or per-tab authorization, so
32515+
// those fields must be absent rather than asserted as ask/false.
32516+
const untabbed = agent._runtimeTraceConfig(cloud);
32517+
assert.equal('mode' in untabbed, false, `${label}: unknown mode should be omitted, not guessed`);
32518+
assert.equal('api_mutations_allowed' in untabbed, false, `${label}: per-tab API authorization should be omitted without a tab`);
32519+
assert.equal('selection_grounded' in untabbed, false, `${label}: selection grounding should be omitted without a tab`);
32520+
assert.equal(untabbed.browser_target, label, `${label}: browser target should survive without a tab`);
3247532521

3247632522
const byoOptions = agent._cloudGenerationOptions({ config: { providerName: 'openai' } }, { temperature: 0 }, { tabId, generationName: 'memory' });
3247732523
assert.deepEqual(byoOptions, { temperature: 0 }, `${label}: BYO provider received Cloud collection fields`);

0 commit comments

Comments
 (0)