diff --git a/src/harnesses/codex/rollout-activity-summary.test.ts b/src/harnesses/codex/rollout-activity-summary.test.ts index 55538e2..1dd7836 100644 --- a/src/harnesses/codex/rollout-activity-summary.test.ts +++ b/src/harnesses/codex/rollout-activity-summary.test.ts @@ -347,6 +347,39 @@ test("summarizes structured local shell and web-search calls", () => { assert.equal(web.external, true); }); +test("summarizes current Codex code-mode web tools and their batched arguments", () => { + for (const toolName of ["web__run", "web_run", "web.run"]) { + const summary = summarizeCodexRolloutActivity( + rollout({ + codeModeTools: [toolName], + callPayload: { + input: `const r = await tools.${toolName}({search_query:[{q:"Codex rollout activity"},{q:"Codex code mode"}],response_length:"long"}); text(r);`, + }, + }), + ); + + assert.equal(summary.kind, "web"); + assert.equal(summary.subject, "Codex rollout activity +1"); + assert.equal(summary.external, true); + } +}); + +test("summarizes non-search web run operations", () => { + const summary = summarizeCodexRolloutActivity( + rollout({ + codeModeTools: ["web__run"], + callPayload: { + input: + 'tools.web__run({open:[{ref_id:"https://sandpi.ai/llms.txt"}]});', + }, + }), + ); + + assert.equal(summary.kind, "web"); + assert.equal(summary.subject, "https://sandpi.ai/llms.txt"); + assert.equal(summary.external, true); +}); + declare global { // Test sentinel proving arbitrary code-mode input is never evaluated. var __sandpiActivityParserExecuted: boolean | undefined; diff --git a/src/harnesses/codex/rollout-activity-summary.ts b/src/harnesses/codex/rollout-activity-summary.ts index 2174f52..070a77d 100644 --- a/src/harnesses/codex/rollout-activity-summary.ts +++ b/src/harnesses/codex/rollout-activity-summary.ts @@ -519,16 +519,24 @@ function safeSubject(args: Record | null) { return null; } +function isWebToolName(name: string) { + return ( + name === "web_run" || + name === "web__run" || + name.startsWith("web.") || + name.includes("web_search") + ); +} + function externalTool(names: string[], activity: CodexRolloutToolActivity) { return ( names.some( (name) => name.startsWith("mcp__") || - name.startsWith("web.") || + isWebToolName(name) || name.startsWith("browser.") || name.startsWith("image_gen.") || name.startsWith("image_gen__") || - name.includes("web_search") || name.includes("imagegen") || name.includes("image_generation"), ) || @@ -551,7 +559,7 @@ function toolKind( return "agent"; } if ( - names.some((item) => item.startsWith("web.") || item.includes("web_search")) || + names.some(isWebToolName) || activity.callType === "web_search_call" ) { return "web"; @@ -670,6 +678,35 @@ function webActionSubject(activity: CodexRolloutToolActivity) { return null; } +function webRunSubject(args: Record | null) { + const candidates: Array<[string, string]> = [ + ["search_query", "q"], + ["image_query", "q"], + ["open", "ref_id"], + ["find", "pattern"], + ["weather", "location"], + ["finance", "ticker"], + ["time", "utc_offset"], + ["sports", "league"], + ]; + for (const [collection, property] of candidates) { + const items = args?.[collection]; + if (!Array.isArray(items)) continue; + const values = items + .map((item) => objectRecord(item)?.[property]) + .filter( + (value): value is string => + typeof value === "string" && value.trim().length > 0, + ); + if (values.length > 0) { + return values.length === 1 + ? values[0]! + : `${values[0]} +${values.length - 1}`; + } + } + return null; +} + export function summarizeCodexRolloutActivity( activity: CodexRolloutToolActivity, ): CodexRolloutActivitySummary { @@ -725,6 +762,7 @@ export function summarizeCodexRolloutActivity( (cellId ? `#${cellId}` : null) ?? (sessionId ? `#${sessionId}` : null) ?? webActionSubject(activity) ?? + (kind === "web" ? webRunSubject(args) : null) ?? safeSubject(args) ?? (names.length > 1 ? names.join(" ยท ") : toolName); const detail = diff --git a/src/harnesses/codex/session-activity.test.ts b/src/harnesses/codex/session-activity.test.ts index f00872e..eb1ca78 100644 --- a/src/harnesses/codex/session-activity.test.ts +++ b/src/harnesses/codex/session-activity.test.ts @@ -488,6 +488,7 @@ test("restores the real 30-call rollout shape alongside its modeled file change" test("categorizes namespaced Codex code-mode tools without cross-harness normalization", () => { const activity = rolloutFeed([ rolloutTool(1, { codeModeTools: ["web.run"] }), + rolloutTool(6, { codeModeTools: ["web__run"] }), rolloutTool(2, { codeModeTools: ["image_gen.imagegen"] }), rolloutTool(3, { codeModeTools: ["collaboration.spawn_agent"] }), rolloutTool(4, { codeModeTools: ["functions.exec_command"] }), @@ -508,10 +509,10 @@ test("categorizes namespaced Codex code-mode tools without cross-harness normali for (const record of activity.records) record.turnId = turnId; assert.deepEqual(summarizeCodexSessionActivity(emptyProjection, activity), { - total: 5, - records: 5, + total: 6, + records: 6, issues: 0, - external: 3, + external: 4, commands: 2, files: 0, agents: 1, diff --git a/src/harnesses/codex/session-activity.ts b/src/harnesses/codex/session-activity.ts index 7d8ef11..0964afc 100644 --- a/src/harnesses/codex/session-activity.ts +++ b/src/harnesses/codex/session-activity.ts @@ -117,6 +117,7 @@ function codexRolloutActivityIsVisible(entry: CodexRolloutToolActivity) { function rolloutToolCategories( entry: CodexRolloutToolActivity, ): CodexSessionActivityCategory[] { + const summary = summarizeCodexRolloutActivity(entry); const names = entry.codeModeTools.length > 0 ? entry.codeModeTools : [entry.name]; const nameVariants = names.flatMap((name) => [ @@ -124,23 +125,9 @@ function rolloutToolCategories( name.split(".").at(-1) ?? name, ]); const categories: CodexSessionActivityCategory[] = []; - const external = - names.some( - (name) => - name.startsWith("mcp__") || - name.startsWith("web.") || - name.startsWith("image_gen.") || - name.startsWith("image_gen__") || - name.startsWith("browser.") || - name.includes("web_search") || - name.includes("imagegen") || - name.includes("image_generation") || - name.includes("browser"), - ) || - entry.callType === "tool_search_call" || - entry.callType === "web_search_call" || - entry.callType === "image_generation_call"; - if (external) categories.push("external"); + if (summary.external || entry.callType === "tool_search_call") { + categories.push("external"); + } if (nameVariants.some((name) => CODEX_ROLLOUT_AGENT_TOOLS.has(name))) { categories.push("agents"); }