Skip to content

feat(web): add configurable tool grouping mode - #1157

Closed
techotaku39 wants to merge 4 commits into
tiann:mainfrom
techotaku39:feat/web-tool-grouping-mode
Closed

feat(web): add configurable tool grouping mode#1157
techotaku39 wants to merge 4 commits into
tiann:mainfrom
techotaku39:feat/web-tool-grouping-mode

Conversation

@techotaku39

@techotaku39 techotaku39 commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Summary

  • add a “Group Tools” chat setting with Classified and Grouped modes
  • keep traditional grouped tool cards as the default while retaining the classified Codex presentation as an option
  • allow grouped mode to combine contiguous Codex command tools into regular collapsed tool groups
  • persist the preference locally and apply it while building visible chat blocks
  • synchronize the preference across hook consumers in the same browser window

Behavior

  • Grouped (default) uses the traditional grouping behavior for contiguous eligible tools, including structured Codex commands.
  • Classified preserves the current Codex behavior: exploration actions use their dedicated presentation while other structured Codex commands remain separate.
  • Changing the setting updates already-mounted chats immediately; no new message or page reload is required.
  • Interactive, planning, milestone, permission, and subagent tools remain outside regular grouping in both modes.
  • The setting is global, but the visible difference currently applies mainly to Codex because Codex is the only backend with a dedicated classified exploration presentation.
  • Other agents continue using regular tool grouping in both modes until backend-specific classified presentations are available.

Default rationale

Grouped remains the default because it matches Hapi's longstanding tool presentation across agent backends. The Codex-specific classified presentation introduced in #1139 is preserved as an optional mode rather than removed.

Testing

  • bun typecheck
  • bun run test:web -- --maxWorkers=4
  • bun run build:web
  • git diff --check

@github-actions github-actions 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.

Findings

  • No issues found.

Summary

  • Review mode: initial
  • Full diff reviewed. No Blocker/Major/Minor/Nit findings above confidence threshold.
  • Residual risk: local automation could not run because bun is unavailable in this runner.

Testing

  • Not run (automation): bun not found when attempting targeted web tests.

HAPI Bot

@github-actions github-actions 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.

Findings

  • No issues found.

Questions

  • None.

Summary

  • Review mode: follow-up after new commits
  • Full latest PR diff reviewed; previous HAPI Bot review at 99dab452 had no findings. No Blocker/Major/Minor/Nit findings above confidence threshold.
  • Residual risk: GitHub checks were still pending at review time.

Testing

  • Not run (automation). GitHub checks observed pending: pr-review, test.

HAPI Bot

@techotaku39

Copy link
Copy Markdown
Contributor Author

Hi @swear01 — I wanted to make you aware of this PR because it builds on the Codex exploration presentation introduced in #1139.

Hapi historically groups tools across agent backends, while #1139 introduced a Codex-specific classified presentation. This PR preserves both behaviors: Grouped remains the default for consistency with the other agents, and Classified keeps the dedicated Exploring/Explored presentation from #1139 as an explicit user option.

No functionality from #1139 is removed. Feedback is welcome, especially if there are Codex-specific cases that should remain classified regardless of the selected mode.

@github-actions github-actions 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.

Findings

  • No issues found.

Questions

  • None.

Summary

  • Review mode: follow-up after new commits
  • Full latest PR diff reviewed; previous HAPI Bot review at c1948757 had no findings. No Blocker/Major/Minor/Nit findings above confidence threshold.
  • Residual risk: web behavior only; I did not execute PR code in this review run.

Testing

  • Not run (automation).

HAPI Bot

swear01 added a commit to swear01/hapi that referenced this pull request Jul 29, 2026

@github-actions github-actions 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.

Findings

  • [Major] Reset previous-group identity when the grouping mode changes - SessionChat recomputes the blocks for the new mode while still passing groups produced by the old mode. A grouped Codex run such as exploration / general command / exploration is one group; switching to Classified splits it into two exploration groups. Because createToolGroupId matches either edge of the old group, both new groups reuse the same ID, which also becomes a duplicate assistant-ui toolCallId. Evidence: web/src/components/SessionChat.tsx:1170; matching logic at web/src/chat/toolGroups.ts:246.

    Suggested fix:

    const previousToolGroupingModeRef = useRef(toolGroupingMode)
    
    // When building:
    previousGroups: previousToolGroupingModeRef.current === toolGroupingMode
        ? visibleGroupsRef.current
        : [],
    
    // When committing the new visible groups:
    useEffect(() => {
        previousToolGroupingModeRef.current = toolGroupingMode
        visibleGroupsRef.current = visibleBlocks.filter(isToolGroupBlock)
    }, [toolGroupingMode, visibleBlocks])

Questions

  • None.

Summary

Review mode: follow-up after new commits

  • Full latest PR diff reviewed. The new head merges origin/main; the nine PR-specific files are unchanged from the prior bot-reviewed commit.
  • One Major mode-transition correctness issue found.

Testing

  • Not run (automation).
  • Add a regression covering Grouped -> Classified for exploration / general command / exploration, asserting all resulting group IDs and tool-call IDs are unique.

HAPI Bot

Comment thread web/src/components/SessionChat.tsx Outdated
hasMoreMessages: props.hasMoreMessages,
previousGroups: visibleGroupsRef.current
previousGroups: visibleGroupsRef.current,
groupingMode: toolGroupingMode

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[MAJOR] Reset cached group IDs when the mode changes

visibleGroupsRef.current contains groups built under the previous mode. For a Codex sequence exploration / general command / exploration, Grouped creates one group; Classified splits it into two. The first split group matches the old group's firstToolId, and the second matches its lastToolId at toolGroups.ts:246, so both reuse the same group ID and emit duplicate assistant-ui toolCallId values.

Suggested fix:

const previousToolGroupingModeRef = useRef(toolGroupingMode)

previousGroups: previousToolGroupingModeRef.current === toolGroupingMode
    ? visibleGroupsRef.current
    : [],

useEffect(() => {
    previousToolGroupingModeRef.current = toolGroupingMode
    visibleGroupsRef.current = visibleBlocks.filter(isToolGroupBlock)
}, [toolGroupingMode, visibleBlocks])

Also add a Grouped -> Classified regression asserting the split groups have unique IDs.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in a92cff49.

SessionChat now tracks the mode associated with its cached groups, and buildVisibleChatBlocks ignores those groups when the mode changes. Added a Grouped -> Classified regression for exploration / general command / exploration and verified that the two classified groups receive unique IDs.

Validated with bun typecheck, the targeted 25 tests, and bun run build:web.

@github-actions github-actions 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.

Findings

  • None.

Questions

  • None.

Summary

Review mode: follow-up after new commits

  • Full latest PR diff reviewed. The prior duplicate group-ID finding is resolved by discarding cached groups across mode changes (web/src/chat/toolGroups.ts:267, web/src/components/SessionChat.tsx:1170) with a regression at web/src/chat/toolGroups.test.ts:262.
  • No Blocker/Major/Minor/Nit findings above the confidence threshold.
  • Residual risk: GitHub checks were still pending at review time.

Testing

  • git diff --check passed.
  • Targeted web tests and web typecheck not run: bun is unavailable in this runner.
  • GitHub checks observed pending: pr-review, test.

HAPI Bot

@techotaku39
techotaku39 force-pushed the feat/web-tool-grouping-mode branch from a92cff4 to b62788b Compare August 7, 2026 00:01

@github-actions github-actions 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.

Findings

  • [Minor] Keep same-window consumers synchronized when persistence fails — the custom change event is inside the localStorage try block, so a SecurityError or quota failure leaves the Settings hook on the selected mode while the already-mounted SessionChat hook remains on the old mode. Evidence: web/src/hooks/useToolGroupingMode.ts:61.

    Suggested fix:

    const onChange = (event: Event) => {
        const detail = (event as CustomEvent<ToolGroupingMode>).detail
        setToolGroupingModeState(parseToolGroupingMode(detail ?? null))
    }
    
    try {
        // Persist mode.
    } catch {
        // Ignore persistence errors.
    }
    window.dispatchEvent(new CustomEvent<ToolGroupingMode>(CHANGE_EVENT, { detail: mode }))

Questions

  • None.

Summary

Review mode: follow-up after new commits

  • Full latest PR diff reviewed; prior HAPI Bot review and the a92cff4...b62788b compare were used only as follow-up context.
  • One Minor finding: the immediate same-window update path depends on successful localStorage persistence.
  • Residual risk: the GitHub test check was still in progress at review time.

Testing

  • Not run (automation; PR code was not executed).
  • git diff --check passed.
  • Suggested regression: mock localStorage.setItem to throw and verify a second useToolGroupingMode consumer still receives the selected mode.

HAPI Bot

} else {
localStorage.setItem(STORAGE_KEY, mode)
}
window.dispatchEvent(new Event(CHANGE_EVENT))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[MINOR] Keep the custom notification independent of localStorage success

The event is dispatched inside the try block. If localStorage.setItem/removeItem throws (blocked storage or quota failure), this hook updates its own state at line 60, but the already-mounted SessionChat hook never receives the new mode. The Settings radio can therefore show Classified while the chat stays Grouped.

Suggested fix:

const onChange = (event: Event) => {
    const detail = (event as CustomEvent<ToolGroupingMode>).detail
    setToolGroupingModeState(parseToolGroupingMode(detail ?? null))
}

try {
    if (mode === DEFAULT_TOOL_GROUPING_MODE) {
        localStorage.removeItem(STORAGE_KEY)
    } else {
        localStorage.setItem(STORAGE_KEY, mode)
    }
} catch {
    // Ignore persistence errors.
}
window.dispatchEvent(new CustomEvent<ToolGroupingMode>(CHANGE_EVENT, { detail: mode }))

Please add a two-consumer regression test with localStorage.setItem throwing.

@techotaku39
techotaku39 marked this pull request as draft August 7, 2026 00:10
@techotaku39

Copy link
Copy Markdown
Contributor Author

Superseded by #1599, which consolidates and extends this work into the unified tool card display settings. Closing this draft to keep review focused on #1599.

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.

1 participant