Skip to content

Carry a vendor-neutral tool kind on daemon tool-call envelopes - #793

Merged
alexeyzimarev merged 1 commit into
mainfrom
capacitor/agent-24e6d347accc4c
Sep 6, 2026
Merged

alexeyzimarev merged 1 commit into
mainfrom
capacitor/agent-24e6d347accc4c

Conversation

@alexeyzimarev

@alexeyzimarev alexeyzimarev commented Sep 6, 2026 •

Copy link
Copy Markdown
Member

Closes #746 — AI-2426

What & why

Every consumer that wants to say what a tool call did keys on the vendor's raw tool name, and the
names disagree per vendor. AcpEventEnvelope gains a nullable ToolKind carrying the ACP tool-kind
vocabulary, populated by the ACP translator and the daemon's Codex app-server lane. ToolName keeps
raw vendor fidelity, since the server pairs exec_command with write_stdin by it. Null means no
lane classified the call; other means none of the above.

This is the daemon half only. #773 replaced the transcript-projection contract: a projected tool
call is now a Kurrent.Agent.Schema ToolCallInfo, which has no kind field, so the Claude/Codex
projections cannot carry one until that package gains it. Tracked as #794 — see the follow-up
issue for the shape it needs.

Where to look

CodexNotificationMapper gains a webSearch case. Without it a hosted web search fell through to
Unmapped and reported other, which would have contradicted every other lane's fetch. Like
fileChange it has no item/started row, so the completed item emits the pair — an orphan tool call
renders as running forever, because only a result settles one.

The Codex kind table is not the one in the issue body; the comment on #746
records what the rollouts actually contain.

Verification

dotnet run per suite: Core 2936 (2926 passed, 9 skipped, 1 contention flake that passes in
isolation — ProcessRunnerTests.KillTree_cancelled_ct_kills_the_child_then_throws), plus the touched
daemon classes — AcpEventTranslatorTests 20, CodexNotificationMapperTests 18,
AcpEventEnvelopeWireCompatTests 11 — 0 failed. dotnet publish -c Release greps 0 IL2026/IL3050.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 6, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-06T11:56:47.620311Z 6b60d3d PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Carry vendor-neutral tool kinds on canonical tool-call events

✨ Enhancement 🐞 Bug fix 🧪 Tests 📝 Documentation 🕐 40+ Minutes

Grey Divider

AI Description

• Adds a closed, nullable ACP tool-kind vocabulary to canonical tool-call events.
• Classifies Claude, Codex, and ACP calls while preserving raw vendor tool names.
• Restores paired Codex web and tool-search projections with stable completion semantics.
Diagram

graph TD
  Claude["Claude Transcript"] --> ClaudeMap["Claude Projection"] --> Envelope["Canonical Envelope"] --> Consumers["Tool Consumers"]
  Codex["Codex Events"] --> Classifier["Shared Classifier"] --> Envelope
  ACP["ACP Updates"] --> Normalizer["Kind Normalizer"] --> Envelope
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Keep consumer-side vendor mappings
  • ➕ Avoids changing the canonical event envelope.
  • ➕ Lets each consumer define task-specific categories independently.
  • ➖ Duplicates vendor mappings across consumers.
  • ➖ Produces inconsistent classifications between live and imported sessions.
  • ➖ Requires every consumer to understand vendor-specific names and command shapes.
2. Normalize ToolName in place
  • ➕ Avoids adding a second tool classification field.
  • ➕ Makes simple consumers depend on one property.
  • ➖ Destroys vendor fidelity needed to pair Codex exec_command and write_stdin events.
  • ➖ Cannot represent both the raw tool identity and semantic behavior.
  • ➖ Risks breaking existing consumers and server-side result handling.
3. Use a serialized enum
  • ➕ Provides compile-time exhaustiveness for in-process consumers.
  • ➕ Makes the closed vocabulary explicit in the type system.
  • ➖ Complicates forward compatibility with ACP vendors introducing unknown values.
  • ➖ Requires custom serialization and nullable handling.
  • ➖ Creates stronger wire-contract coupling than string constants plus normalization.

Recommendation: The PR's additive nullable ToolKind field is the best approach. It centralizes cross-vendor semantics while retaining ToolName for vendor-specific pairing, and string constants with normalization preserve wire compatibility and distinguish unclassified calls from classified other calls. Synthesizing a settled pair for id-less Codex web searches also matches the canonical call/result lifecycle better than emitting an indefinitely running call.

Files changed (15) +371 / -23

Enhancement (5) +74 / -6
ClaudeTranscriptEvents.csClassify Claude tool calls by built-in name +13/-0

Classify Claude tool calls by built-in name

• Adds a ToolKind to every projected Claude tool call. Known built-ins map to read, edit, execute, search, or fetch, while unrecognized and MCP tools map to other.

src/Capacitor.Cli.Core/Harness/Claude/ClaudeTranscriptEvents.cs

CodexCommandClassifier.csExpose shared Codex shell-kind classification +11/-0

Expose shared Codex shell-kind classification

• Adds ShellToolKind to translate command hints into ACP read, search, or execute kinds. Unclassifiable shell commands remain execute because they still ran a command.

src/Capacitor.Cli.Core/Harness/Codex/CodexCommandClassifier.cs

Models.csAdd the canonical ACP tool-kind contract +33/-0

Add the canonical ACP tool-kind contract

• Defines the ten ACP tool-kind tokens and normalization rules for unknown, blank, and absent values. Extends AcpEventEnvelope with an additive nullable ToolKind while retaining the existing contract version.

src/Capacitor.Cli.Core/Models.cs

AcpEventTranslator.csNormalize agent-supplied ACP tool kinds +5/-1

Normalize agent-supplied ACP tool kinds

• Copies ACP tool-call kinds into canonical envelopes after normalization. Unknown supplied values become other, while absent values remain null.

src/Capacitor.Cli.Daemon/Acp/AcpEventTranslator.cs

CodexNotificationMapper.csClassify hosted Codex notification tool calls +12/-5

Classify hosted Codex notification tool calls

• Adds edit, shell-derived, and other kinds to hosted Codex calls, including ephemeral patches and unmapped items. The shared classifier keeps live app-server events consistent with imported rollout events.

src/Capacitor.Cli.Daemon/Harness/Codex/CodexNotificationMapper.cs

Refactor (3) +40 / -15
ToolSummary.csReuse shared shell command extraction +1/-13

Reuse shared shell command extraction

• Replaces the view model's private command-input parser with ToolCommandText so UI categorization and canonical classification interpret vendor command shapes consistently.

src/Capacitor.App/ViewModels/ToolSummary.cs

ToolCommandText.csCentralize cross-vendor command extraction +33/-0

Centralize cross-vendor command extraction

• Introduces helpers for extracting shell commands from parsed or serialized tool inputs, including cmd, string command, and argv representations. Invalid or non-object JSON safely returns null.

src/Capacitor.Cli.Core/ToolCommandText.cs

AcpPermissionPresets.csShare ACP vocabulary constants in permission presets +6/-2

Share ACP vocabulary constants in permission presets

• Replaces duplicated read, search, edit, move, and delete literals with AcpToolKind constants without changing preset behavior.

src/Capacitor.Cli.Daemon/Acp/AcpPermissionPresets.cs

Tests (5) +171 / -0
AcpEventEnvelopeWireCompatTests.csVerify tool-kind wire compatibility and vocabulary +31/-0

Verify tool-kind wire compatibility and vocabulary

• Covers ToolKind serialization and round-tripping, pins all ACP vocabulary constants, and verifies normalization preserves null while closing unknown values to other.

test/Capacitor.Cli.Core.Tests.Unit/AcpEventEnvelopeWireCompatTests.cs

ClaudeTranscriptEventsTests.csVerify Claude tool-kind mappings +29/-0

Verify Claude tool-kind mappings

• Tests every supported Claude name mapping, unrecognized-tool fallback, and the rule that Bash remains execute even when its command reads a file.

test/Capacitor.Cli.Core.Tests.Unit/Harness/Claude/ClaudeTranscriptEventsTests.cs

CodexRolloutEventsTests.csVerify Codex kinds and search-event pairing +76/-0

Verify Codex kinds and search-event pairing

• Covers name- and command-based Codex classification, including sandbox and MCP behavior. It also verifies synthesized web-search pairs, tool-search call/result correlation, and capped registry output.

test/Capacitor.Cli.Core.Tests.Unit/Harness/Codex/CodexRolloutEventsTests.cs

AcpEventTranslatorTests.csVerify ACP tool-kind translation and normalization +13/-0

Verify ACP tool-kind translation and normalization

• Asserts translated calls carry recognized kinds, unknown supplied kinds become other, and missing kinds remain null.

test/Capacitor.Cli.Daemon.Tests.Unit/Acp/AcpEventTranslatorTests.cs

CodexNotificationMapperTests.csVerify hosted Codex classification parity +22/-0

Verify hosted Codex classification parity

• Tests read, search, execute, edit, and MCP classifications from hosted Codex notifications to ensure parity with rollout projection behavior.

test/Capacitor.Cli.Daemon.Tests.Unit/Harness/Codex/CodexNotificationMapperTests.cs

Documentation (1) +37 / -0
CHANGES.mdDocument canonical tool-kind semantics and vendor mappings +37/-0

Document canonical tool-kind semantics and vendor mappings

• Explains the closed ACP vocabulary, null-versus-other semantics, Claude and Codex classification rules, hosted-lane consistency, and synthesized Codex search events.

docs/CHANGES.md

Other (1) +49 / -2
CodexRolloutEvents.csClassify Codex calls and restore search projections +49/-2

Classify Codex calls and restore search projections

• Adds vendor-neutral kinds to projected Codex calls using names or shell-command analysis. It also projects web searches as settled call/result pairs and tool-registry searches as paired other-kind events.

src/Capacitor.Cli.Core/Harness/Codex/CodexRolloutEvents.cs

@qodo-code-review

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (2) 📘 Rule violations (3) 📜 Skill insights (0)

Grey Divider


Action required

1. Users lose distinct web search records 🐞 Bug ≡ Correctness
Description
WebSearch derives its synthesized ToolCallId only from timestamp, falling back to a query,
URL, or the shared literal 0, and reuses that value for both envelopes. Two web-search records
with the same timestamp, or timestamp-less records with the same action or no action, therefore
reach ID-pairing transcript consumers as the same call.
Code

src/Capacitor.Cli.Core/Harness/Codex/CodexRolloutEvents.cs[R71-73]

+        var action = payload.Obj("action");
+        var status = payload.Str("status") ?? "";
+        var id     = $"web_search:{ts ?? action?.Str("query") ?? action?.Str("url") ?? "0"}";
Relevance

●● Moderate

Collision risk is technically plausible, but the PR documents serial millisecond timestamps and
tests distinct timestamped records.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The new projection emits a call and result using an ID based only on timestamp/action fallback data.
The projection contract explicitly says downstream consumers pair calls and results by ID, so equal
IDs make distinct searches indistinguishable.

src/Capacitor.Cli.Core/Harness/Codex/CodexRolloutEvents.cs[70-81]
src/Capacitor.Cli.Core/TranscriptProjection.cs[8-11]
test/Capacitor.Cli.Core.Tests.Unit/Harness/Codex/CodexRolloutEventsTests.cs[9-10]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Web-search calls without vendor IDs can receive the same synthesized `ToolCallId`, causing ID-based consumers to treat distinct calls as one.

## Issue Context
The rollout envelope already carries an `ordinal` field, but `WebSearch` currently builds its ID from timestamp/action data only. Preserve the same generated ID on the paired result while incorporating a per-record discriminator.

## Fix Focus Areas
- src/Capacitor.Cli.Core/Harness/Codex/CodexRolloutEvents.cs[24-32]
- src/Capacitor.Cli.Core/Harness/Codex/CodexRolloutEvents.cs[70-81]
- test/Capacitor.Cli.Core.Tests.Unit/Harness/Codex/CodexRolloutEventsTests.cs[77-106]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. Maintainers cannot retire old parsing 📘 Rule violation ⚙ Maintainability
Description
ToolCommandText labels the supported command array as the older argv form without naming the
persisted format or supported producer that can still emit it. When that compatibility branch is
revisited, the comment gives no evidence for whether removing or retaining it will affect current
sessions.
Code

src/Capacitor.Cli.Core/ToolCommandText.cs[7]

+/// and the older argv form is a `bash -lc <script>` triple.
Relevance

●●● Strong

Recent history accepts removing time-relative or undocumented historical narrative from source
comments.

PR-#685
PR-#667
PR-#693

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Compliance rule 2897945 permits comments about old shapes only when they identify a concrete source
that can still produce the form; the added comment calls the argv representation older but names no
such source.

Rule 2897945: Reference deprecated shapes in comments only when they are still observable in the running system
src/Capacitor.Cli.Core/ToolCommandText.cs[5-11]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The comment calls the argv input form older without identifying the current source that requires compatibility support.

## Issue Context
Name the persisted format or supported producer that still emits this shape. If no current source exists, remove the historical qualifier and describe only the accepted input contract.

## Fix Focus Areas
- src/Capacitor.Cli.Core/ToolCommandText.cs[5-11]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Failed tool searches look successful 🐞 Bug ≡ Correctness
Description
CodexRolloutEvents.Project creates each tool_search_output result without mapping its status
or error to ToolIsError, so the field remains false. When Codex reports a failed tool-registry
lookup, the chat consumer consequently marks the paired call Done, unlike the hosted Codex result
path, which recognizes either an error payload or status == "failed".
Code

src/Capacitor.Cli.Core/Harness/Codex/CodexRolloutEvents.cs[R34-35]

+                "tool_search_output"
+                                   => [new AcpEventEnvelope(Kind: AcpEventKind.ToolResult, ToolCallId: payload.Str("call_id"), ToolResult: Cap(payload.Arr("tools")?.GetRawText() ?? ""), TimestampIso: ts)],
Relevance

●●● Strong

Missing failure propagation is a concrete correctness bug; analogous tool-result error handling is
explicitly preserved elsewhere.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The added tool_search_output projection reads the tool list but does not inspect the event's
status or error when constructing ToolResult, while the test fixture confirms that this event
shape carries status. The chat UI chooses success or failure exclusively from ToolIsError and maps
false to Done, whereas the existing hosted Codex mapper treats either an error payload or `status
== "failed"` as failure, demonstrating the metadata and semantics the rollout projection must
preserve.

src/Capacitor.Cli.Core/Harness/Codex/CodexRolloutEvents.cs[26-35]
src/Capacitor.App/ViewModels/ChatTabViewModel.cs[395-400]
src/Capacitor.Cli.Daemon/Harness/Codex/CodexNotificationMapper.cs[340-352]
test/Capacitor.Cli.Core.Tests.Unit/Harness/Codex/CodexRolloutEventsTests.cs[121-127]
src/Capacitor.Cli.Core/Harness/Codex/CodexRolloutEvents.cs[33-35]
test/Capacitor.Cli.Core.Tests.Unit/Harness/Codex/CodexRolloutEventsTests.cs[121-124]
src/Capacitor.Cli.Daemon/Harness/Codex/CodexNotificationMapper.cs[128-145]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description

`tool_search_output` records discard their failure status and error payload when creating a `ToolResult`, leaving `ToolIsError == false`. This makes failed tool-registry searches appear successful to consumers.

## Issue Context

The hosted Codex mapper already treats either an `error` payload or `status == "failed"` as failure, while the chat UI determines the displayed outcome solely from `ToolIsError` and maps false to `Done`. Apply equivalent failure semantics to rollout tool-search outputs, preserve useful error text when available, and cover failed-status behavior in the unit tests.

## Fix Focus Areas

- src/Capacitor.Cli.Core/Harness/Codex/CodexRolloutEvents.cs[26-36]
- src/Capacitor.Cli.Daemon/Harness/Codex/CodexNotificationMapper.cs[128-145]
- src/Capacitor.App/ViewModels/ChatTabViewModel.cs[395-400]
- test/Capacitor.Cli.Core.Tests.Unit/Harness/Codex/CodexRolloutEventsTests.cs[108-128]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


4. Tool parsing drifts from JSON helpers 📘 Rule violation ⚙ Maintainability
Description
ToolCommandText.From(JsonElement) filters argv entries with p.ValueKind == JsonValueKind.String
instead of the available p.IsString extension. Every array-form command reaches this filter, so a
change to the shared shape-checking policy can leave this cross-vendor path behaving differently.
Code

src/Capacitor.Cli.Core/ToolCommandText.cs[17]

+        var parts = argv.EnumerateArray().Where(p => p.ValueKind == JsonValueKind.String).Select(p => p.GetString()!).ToList();
Evidence
Compliance rule 2270023 requires changed JsonElement shape checks to use JsonElementExtensions;
the added filter directly compares ValueKind even though IsString is available.

Rule 2270023: Use JsonElementExtensions helpers instead of direct JsonValueKind comparisons
src/Capacitor.Cli.Core/ToolCommandText.cs[17-17]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`ToolCommandText.From(JsonElement)` directly compares `JsonElement.ValueKind` even though the shared `IsString` helper provides the required check.

## Issue Context
Replace the direct string-kind comparison without changing how non-string argv entries are ignored.

## Fix Focus Areas
- src/Capacitor.Cli.Core/ToolCommandText.cs[17-17]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View medium (1)
5. Maintainers face split vendor parsing 📘 Rule violation ⌂ Architecture
Description
CodexRolloutEvents.Project adds interpretation of raw Codex web_search_call, tool_search_call,
and tool_search_output payloads in Capacitor.Cli.Core/Harness/Codex rather than the designated
transcript harness assembly. A Codex format change must therefore be coordinated outside the
canonical harness boundary, and downstream code can no longer assume vendor fields are confined
there.
Code

src/Capacitor.Cli.Core/Harness/Codex/CodexRolloutEvents.cs[R32-35]

+                "web_search_call"  => WebSearch(payload, ts),
+                "tool_search_call" => [new AcpEventEnvelope(Kind: AcpEventKind.ToolCall, ToolCallId: payload.Str("call_id"), ToolName: "tool_search", ToolInputJson: payload.Obj("arguments")?.GetRawText() ?? "{}", ToolKind: AcpToolKind.Other, TimestampIso: ts)],
+                "tool_search_output"
+                                   => [new AcpEventEnvelope(Kind: AcpEventKind.ToolResult, ToolCallId: payload.Str("call_id"), ToolResult: Cap(payload.Arr("tools")?.GetRawText() ?? ""), TimestampIso: ts)],
Relevance

●● Moderate

Architecture concern is plausible, but no close precedent confirms this boundary is enforced for
rollout projections.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Rules 2762984 and 3114705 place transcript projections and raw vendor-format interpretation in
Capacitor.Models.Transcripts/Harness/<Vendor>. The added switch arms recognize raw Codex record
names and extract their vendor-specific fields from a file under Capacitor.Cli.Core/Harness/Codex.

Rule 2762984: Place vendor-specific harness code only in the correct Harness/&lt;Vendor&gt;/ assembly and directory
Rule 3114705: Vendor transcript formats must be parsed only in designated harness directory
src/Capacitor.Cli.Core/Harness/Codex/CodexRolloutEvents.cs[32-35]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
New raw Codex transcript record types and fields are interpreted in the CLI Core harness instead of the designated transcript harness assembly.

## Issue Context
Keep vendor-format parsing and canonical projection within `Capacitor.Models.Transcripts/Harness/Codex`; expose only normalized events to CLI Core consumers.

## Fix Focus Areas
- src/Capacitor.Cli.Core/Harness/Codex/CodexRolloutEvents.cs[32-35]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Context sources
✅ Compliance rules (platform): 58 rules
Review mode: 🧠 Deep: This is a cross-cutting behavioral change across Core projections, ACP translation, daemon mapping, serialization compatibility, and synthesized event pairing, with substantial independent logic and multiple easy-to-miss classification and correlation edge cases.

Grey Divider

Tip of the day
💡 Did you know, you can show, collapse, or hide each part of a finding: code, evidence, and all

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

if (root.Str("cmd") is { } cmd) return cmd;
if (root.Str("command") is { } command) return command;
if (root.Arr("command") is not { } argv) return null;
var parts = argv.EnumerateArray().Where(p => p.ValueKind == JsonValueKind.String).Select(p => p.GetString()!).ToList();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

1. Tool parsing drifts from json helpers 📘 Rule violation ⚙ Maintainability

ToolCommandText.From(JsonElement) filters argv entries with p.ValueKind == JsonValueKind.String
instead of the available p.IsString extension. Every array-form command reaches this filter, so a
change to the shared shape-checking policy can leave this cross-vendor path behaving differently.
Agent Prompt
## Issue description
`ToolCommandText.From(JsonElement)` directly compares `JsonElement.ValueKind` even though the shared `IsString` helper provides the required check.

## Issue Context
Replace the direct string-kind comparison without changing how non-string argv entries are ignored.

## Fix Focus Areas
- src/Capacitor.Cli.Core/ToolCommandText.cs[17-17]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


/// The command a shell tool call was given, dug out of the tool input every vendor shapes
/// differently. Cross-vendor on purpose: Codex writes `cmd`, Claude writes `command` as a string,
/// and the older argv form is a `bash -lc &lt;script&gt;` triple.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

2. Maintainers cannot retire old parsing 📘 Rule violation ⚙ Maintainability

ToolCommandText labels the supported command array as the older argv form without naming the
persisted format or supported producer that can still emit it. When that compatibility branch is
revisited, the comment gives no evidence for whether removing or retaining it will affect current
sessions.
Agent Prompt
## Issue description
The comment calls the argv input form older without identifying the current source that requires compatibility support.

## Issue Context
Name the persisted format or supported producer that still emits this shape. If no current source exists, remove the historical qualifier and describe only the accepted input contract.

## Fix Focus Areas
- src/Capacitor.Cli.Core/ToolCommandText.cs[5-11]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +32 to +35
"web_search_call" => WebSearch(payload, ts),
"tool_search_call" => [new AcpEventEnvelope(Kind: AcpEventKind.ToolCall, ToolCallId: payload.Str("call_id"), ToolName: "tool_search", ToolInputJson: payload.Obj("arguments")?.GetRawText() ?? "{}", ToolKind: AcpToolKind.Other, TimestampIso: ts)],
"tool_search_output"
=> [new AcpEventEnvelope(Kind: AcpEventKind.ToolResult, ToolCallId: payload.Str("call_id"), ToolResult: Cap(payload.Arr("tools")?.GetRawText() ?? ""), TimestampIso: ts)],

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

3. Maintainers face split vendor parsing 📘 Rule violation ⌂ Architecture

CodexRolloutEvents.Project adds interpretation of raw Codex web_search_call, tool_search_call,
and tool_search_output payloads in Capacitor.Cli.Core/Harness/Codex rather than the designated
transcript harness assembly. A Codex format change must therefore be coordinated outside the
canonical harness boundary, and downstream code can no longer assume vendor fields are confined
there.
Agent Prompt
## Issue description
New raw Codex transcript record types and fields are interpreted in the CLI Core harness instead of the designated transcript harness assembly.

## Issue Context
Keep vendor-format parsing and canonical projection within `Capacitor.Models.Transcripts/Harness/Codex`; expose only normalized events to CLI Core consumers.

## Fix Focus Areas
- src/Capacitor.Cli.Core/Harness/Codex/CodexRolloutEvents.cs[32-35]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +34 to +35
"tool_search_output"
=> [new AcpEventEnvelope(Kind: AcpEventKind.ToolResult, ToolCallId: payload.Str("call_id"), ToolResult: Cap(payload.Arr("tools")?.GetRawText() ?? ""), TimestampIso: ts)],

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

4. Failed tool searches look successful 🐞 Bug ≡ Correctness

CodexRolloutEvents.Project creates each tool_search_output result without mapping its status
or error to ToolIsError, so the field remains false. When Codex reports a failed tool-registry
lookup, the chat consumer consequently marks the paired call Done, unlike the hosted Codex result
path, which recognizes either an error payload or status == "failed".
Agent Prompt
## Issue description

`tool_search_output` records discard their failure status and error payload when creating a `ToolResult`, leaving `ToolIsError == false`. This makes failed tool-registry searches appear successful to consumers.

## Issue Context

The hosted Codex mapper already treats either an `error` payload or `status == "failed"` as failure, while the chat UI determines the displayed outcome solely from `ToolIsError` and maps false to `Done`. Apply equivalent failure semantics to rollout tool-search outputs, preserve useful error text when available, and cover failed-status behavior in the unit tests.

## Fix Focus Areas

- src/Capacitor.Cli.Core/Harness/Codex/CodexRolloutEvents.cs[26-36]
- src/Capacitor.Cli.Daemon/Harness/Codex/CodexNotificationMapper.cs[128-145]
- src/Capacitor.App/ViewModels/ChatTabViewModel.cs[395-400]
- test/Capacitor.Cli.Core.Tests.Unit/Harness/Codex/CodexRolloutEventsTests.cs[108-128]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +71 to +73
var action = payload.Obj("action");
var status = payload.Str("status") ?? "";
var id = $"web_search:{ts ?? action?.Str("query") ?? action?.Str("url") ?? "0"}";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

5. Users lose distinct web search records 🐞 Bug ≡ Correctness

WebSearch derives its synthesized ToolCallId only from timestamp, falling back to a query,
URL, or the shared literal 0, and reuses that value for both envelopes. Two web-search records
with the same timestamp, or timestamp-less records with the same action or no action, therefore
reach ID-pairing transcript consumers as the same call.
Agent Prompt
## Issue description
Web-search calls without vendor IDs can receive the same synthesized `ToolCallId`, causing ID-based consumers to treat distinct calls as one.

## Issue Context
The rollout envelope already carries an `ordinal` field, but `WebSearch` currently builds its ID from timestamp/action data only. Preserve the same generated ID on the paired result while incorporating a per-record discriminator.

## Fix Focus Areas
- src/Capacitor.Cli.Core/Harness/Codex/CodexRolloutEvents.cs[24-32]
- src/Capacitor.Cli.Core/Harness/Codex/CodexRolloutEvents.cs[70-81]
- test/Capacitor.Cli.Core.Tests.Unit/Harness/Codex/CodexRolloutEventsTests.cs[77-106]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6b60d3db31

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

return One(new AcpEventEnvelope(
Kind: AcpEventKind.ToolCall, ToolCallId: id, ToolName: type ?? "unknown",
ToolInputJson: item.GetRawText(), ItemId: id, TimestampIso: ts));
ToolInputJson: item.GetRawText(), ToolKind: AcpToolKind.Other,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Classify hosted web searches as fetch operations

When the Codex app server completes a normal webSearch thread item, MapItemCompleted has no matching case, so it reaches Unmapped and this line stamps the call as other. The rollout projection added by this commit classifies the equivalent web_search_call as fetch, so a live hosted session and a later import of that session report different tool kinds, defeating the promised vendor-neutral classification and skewing consumers of the field. Add explicit hosted webSearch handling that emits fetch.

Useful? React with 👍 / 👎.

Comment on lines +34 to +35
"tool_search_output"
=> [new AcpEventEnvelope(Kind: AcpEventKind.ToolResult, ToolCallId: payload.Str("call_id"), ToolResult: Cap(payload.Arr("tools")?.GetRawText() ?? ""), TimestampIso: ts)],

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve failures from tool-search outputs

When a tool_search_output carries a failed status, this result is still emitted with the default ToolIsError = false. That records a failed tool-registry lookup as successful and gives consumers an incorrect outcome; derive ToolIsError from the payload status, as the adjacent synthesized web-search result does.

Useful? React with 👍 / 👎.

ToolName stays the raw vendor name — the server pairs Codex's exec_command with write_stdin by it.
A codex shell call has no name of its own for a read or a search, so its command decides. A hosted
web search has no item/started row, so it surfaces as a pair: an orphan tool call renders as running
forever. The projections cannot carry the kind yet — a tool call is a Kurrent.Agent.Schema
ToolCallInfo, so that half needs a schema field first.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@alexeyzimarev
alexeyzimarev force-pushed the capacitor/agent-24e6d347accc4c branch from 6b60d3d to 044e9c2 Compare September 6, 2026 17:51
@alexeyzimarev alexeyzimarev changed the title Carry a vendor-neutral tool kind on canonical tool-call events Carry a vendor-neutral tool kind on daemon tool-call envelopes Sep 6, 2026
@alexeyzimarev
alexeyzimarev merged commit 7883a5f into main Sep 6, 2026
6 checks passed
@alexeyzimarev
alexeyzimarev deleted the capacitor/agent-24e6d347accc4c branch September 6, 2026 18:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Carry a vendor-neutral tool kind on canonical tool-call events

1 participant