Skip to content

feat(web): render AgentSwarm as an inline tool card - #1425

Merged
wbxl2000 merged 6 commits into
mainfrom
feat/swarm-inline-tool-card
Jul 6, 2026
Merged

feat(web): render AgentSwarm as an inline tool card#1425
wbxl2000 merged 6 commits into
mainfrom
feat/swarm-inline-tool-card

Conversation

@wbxl2000

@wbxl2000 wbxl2000 commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator

Related Issue

No linked issue — see Problem below.

Problem

The legacy AgentSwarm render used a bottom SwarmCard footer plus a
suppressed inline card, gated on live-only fields (swarmIndex /
parentToolCallId) that only the subagent.spawned WebSocket path sets.
Two bugs followed:

  • Footer layout: .content-wrap had min-height: 100% inside a flex
    scroll container, so overflow content compressed back to viewport height
    and pinned the footer near the top of the conversation.
  • Data loss on refresh / resync: REST snapshots don't carry foreground
    subagent tasks, and the projector's reset() replaced accumulated tasks
    with skeleton ones that drop identity metadata, so the footer never
    appeared (or vanished mid-flight) and a stray inline card could render.

What changed

  • Render AgentSwarm as a single dedicated inline tool card (SwarmTool):
    a phase overview plus a per-subagent accordion. Each subagent row shows
    state + name + one-line activity; clicking it expands its full output.
    Live rows come from the AppTask store; once the tool result lands — and
    after a refresh that has already dropped the live tasks — the same rows
    are rebuilt from the parsed <agent_swarm_result> payload.
  • Remove the footer SwarmCard, the messagesToTurns live-swarm skip,
    and the plumbing that passed subagentTasks into messagesToTurns /
    useSideChat. The swarm prop wiring from App to ConversationPane
    is dropped too.
  • Harden .content-wrap with flex-shrink: 0 so siblings after
    <ChatPane> no longer compress it back to viewport height.
  • Keep taskCreated from overwriting swarmIndex / parentToolCallId /
    subagentType / runInBackground with undefined when a resync ships a
    skeleton task over an accumulated one.
  • Add a parseSwarmResult helper plus tests so the refresh path can
    reconstruct the subagent roster from the tool result.

Checklist

  • I have read the CONTRIBUTING document.
  • I have linked a related issue, or explained the problem above.
  • I have added tests that prove my feature works.
  • Ran gen-changesets skill, or this PR needs no changeset.
  • Ran gen-docs skill, or this PR needs no doc update.

Replace the bottom SwarmCard footer and the messagesToTurns live-skip
with one dedicated inline tool card for AgentSwarm. The card shows a
phase overview plus a per-subagent accordion: live progress while it
runs, parsed aggregated result once it completes (and after a refresh
that has already dropped the live tasks).

Refresh and resync keep member identity metadata (swarmIndex,
parentToolCallId, subagentType, runInBackground) stable across skeleton
task replacement in the reducer, and the .content-wrap flex layout is
hardened against the overflow compression that previously displaced the
footer.
@changeset-bot

changeset-bot Bot commented Jul 6, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 77f0c5e

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@moonshot-ai/kimi-code Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@pkg-pr-new

pkg-pr-new Bot commented Jul 6, 2026

Copy link
Copy Markdown
pnpm dlx https://pkg.pr.new/@moonshot-ai/kimi-code@77f0c5e
npx https://pkg.pr.new/@moonshot-ai/kimi-code@77f0c5e

commit: 77f0c5e

@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: becaaa5e9c

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

</div>
</template>

<div v-else class="waiting">{{ t('tools.swarm.waiting') }}</div>

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 Show AgentSwarm error output when parsing fails

When AgentSwarm fails before producing <agent_swarm_result> (for example invalid swarm args such as missing prompt_template, or another runSwarm validation error), the tool result is plain text in tool.output with status === 'error'. This fallback ignores that output and renders “Waiting for subagents…”, so the expanded card hides the actual failure reason from the user instead of showing the error text.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in 354b6c7. Body now renders the parsed <agent_swarm_result> subagents in the accordion with the live members, and when none are available, falls back to the raw tool output (ok or error) instead of 'Waiting for subagents…'. See buildSwarmCardRows / fallbackOutput.

Comment thread apps/kimi-web/src/App.vue Outdated
// AppTask store so the card shows each subagent's live phase; on refresh the
// tasks are gone and the card falls back to the parsed tool result.
provide('resolveSwarmMembers', (toolCallId: string): SwarmMember[] => {
return client.swarms.value.find((group) => group.id === toolCallId)?.members ?? [];

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 Source live members for single resume swarms

When AgentSwarm is used with a single resume_agent_ids entry, the core tool allows it, but client.swarms is built by buildSwarmGroups() which filters out groups with only one member. This new resolver therefore returns [] for that valid running swarm, so the inline card cannot show the resumed subagent’s live phase/progress until a final structured result arrives.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed. resolveSwarmMembers now sources from a dedicated AppTask-store bucket keyed by parentToolCallId (swarmMembersByToolCall), which keeps single-member swarms — including a resume-only AgentSwarm — instead of buildSwarmGroups's >1 filter. badge counter still uses buildSwarmGroups.

- SwarmTool: when AgentSwarm fails before producing a structured
  agent_swarm_result (e.g. argument validation), render the raw tool
  output instead of the "waiting for subagents" placeholder so the
  failure cause is visible.
- resolveSwarmMembers: source live members from the AppTask store keyed
  by parentToolCallId rather than buildSwarmGroups, which filters out
  single-member groups. A resume-only AgentSwarm now streams its live
  progress before the final result arrives. The badge counter still
  relies on buildSwarmGroups's filter.
@wbxl2000

wbxl2000 commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator Author

@codex

@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: e960b566c0

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

subagentType: task.subagentType,
phase: phaseForTask(task),
summary: task.outputPreview,
outputLines: task.outputLines,

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 streamed subagent text for swarm rows

When a swarm subagent streams normal assistant output, the projector sends taskProgress with kind: 'text' and the reducer accumulates it on AppTask.text rather than outputLines. This new live-member map drops that field, so SwarmTool can only derive activity/body from tool progress or the final outputPreview; agents that are actively composing text show an empty/ stale row until completion. Please carry task.text through SwarmMember and prefer it when rendering the live row body/activity.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed. Added text to SwarmMember and threaded it through buildSwarmGroups and swarmMembersByToolCall. SwarmTool prefers member.text for both the row activity preview and the expanded body so a still-streaming subagent shows ongoing output instead of an empty / stale row.

Swarm subagents that stream normal assistant output accumulate it on
AppTask.text (text-kind taskProgress), not outputLines. The new live
member map was dropping `text`, so a still-composing subagent rendered
an empty / stale row until the structured result arrived.

- Add `text` to SwarmMember and thread it through buildSwarmGroups and
  swarmMembersByToolCall.
- SwarmTool: prefer member.text for both the row activity preview and
  the expanded body; fall back to outputLines / summary.
- Tests cover text propagation through both helpers.
@wbxl2000

wbxl2000 commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator Author

@codex

@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: 6211c831c2

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

// One accordion row per subagent. Prefer the live members (each one's live
// phase); fall back to the parsed result when the AppTask store is gone.
const rows = computed<Row[]>(() => {
if (hasMembers.value) {

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 Prefer final swarm results over partial live members

When an AgentSwarm has produced a parsed result but at least one AppTask still exists, this branch returns only live members and never includes result.subagents. In interrupted swarms, the result can contain state="not_started" / outcome="aborted" entries for items that never spawned a task, so the inline card omits those rows and undercounts progress until a refresh drops the tasks. Once result is present, merge missing result rows or prefer the result for terminal entries.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in 354b6c7. Extracted the row model into buildSwarmCardRows(members, result) and merged result-only aborted/not-started rows on top of the live member rows, so interrupted swarms that have a parsed result but still some live AppTasks no longer drop subagents that never spawned a task.


<div v-else-if="errorOutput" class="error-output">{{ errorOutput }}</div>

<div v-else class="waiting">{{ t('tools.swarm.waiting') }}</div>

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 Show raw AgentSwarm output instead of waiting

For a completed AgentSwarm whose output is not recognized by parseSwarmResult—for example a legacy transcript or any parser miss—rows is empty and errorOutput is suppressed because the status is not error, so this renders “Waiting for subagents…” even though tool.output contains final text. That regresses completed swarm calls from showing any result; use the raw output as a fallback when the tool is no longer running.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in 354b6c7. Renamed errorOutput -> fallbackOutput; whenever the tool is no longer running and produced no structured result (ok or error, parser miss, or legacy output), the raw tool output is shown. Covered by unit tests.

Address the two latest swarm review comments:

- Rows: when a parsed agent_swarm_result coexists with live AppTasks
  (which the detail panel also depends on), the inline card previously
  only rendered the live members. Interrupted swarms can carry
  state="not_started" / outcome="aborted" result entries for items that
  never spawned a task; those rows were dropped until a refresh cleared
  the live tasks. Extract the row model into buildSwarmCardRows and
  merge result-only aborted/not-started rows with the live member rows.
- Fallback: when the tool is no longer running but produced no
  structured result (argument validation, parser miss, or legacy
  legacy transcript), render the raw tool output instead of
  "Waiting for subagents…" so the final text / failure cause is
  visible to the user.
@wbxl2000

wbxl2000 commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator Author

@codex

@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: 7592612cae

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".


const SUMMARY_RE = /<summary>([\s\S]*?)<\/summary>/;
const RESUME_HINT_RE = /<resume_hint>([\s\S]*?)<\/resume_hint>/;
const SUBAGENT_RE = /<subagent\b([^>]*)>([\s\S]*?)<\/subagent>/g;

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 Avoid truncating subagent bodies on XML-like output

When a subagent's final text contains a literal </subagent> sequence (for example while analyzing or emitting an AgentSwarm/XML snippet), the AgentSwarm producer writes that body text unescaped, so this non-greedy regex treats the embedded text as the end of the row. In the result-only path used after refresh, the inline swarm card will then lose the rest of that subagent output and can misparse following rows; parse the top-level result format more defensively or escape/unescape body content.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in 354b6c7. Rewrote parseSwarmResult to scan opening tags and resolve each row's body as everything up to the last before the next row's opening tag (or doc end), so a subagent whose body contains a literal is no longer truncated. Tests cover both a single body and sibling rows.

Producer writes subagent body unescaped, so a subagent that analyzes or
emits an AgentSwarm snippet can include a literal "</subagent>" inside
its body. The non-greedy regex treated that as the row close and truncated
the body in the result-only path (post-refresh where the AppTask store is
gone).

Rewrite the parser to scan opening tags, then resolve each row's body as
everything up to the last "</subagent>" before the next row's opening tag
(or document end), preserving embedded close-tag strings. Add tests for a
literal "</subagent>" within a single body and across sibling rows.
@wbxl2000

wbxl2000 commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator Author

@codex

@wbxl2000

wbxl2000 commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator Author

@codex All P2 review comments addressed in 354b6c7f. Summary of fixes on top of the original feat commit:

  • 354b6c7f — parseSwarmResult parses subagent bodies defensively so a literal </subagent> inside a subagent output no longer truncates the body (post-refresh result-only path).
  • 7592612cbuildSwarmCardRows merges result-only aborted/not_started rows with live members so interrupted swarms do not drop rows; when the tool is no longer running and produced no structured result, the raw tool output is shown instead of "Waiting for subagents…".
  • 6211c831SwarmMember.text carries streamed subagent output and SwarmTool prefers it for live activity/expanded body.
  • e960b566resolveSwarmMembers sources live members keyed by parentToolCallId (including single-member resume swarms); SwarmTool shows raw output on error before a structured result exists.

Please re-review. CI is currently green (build / lint / typecheck / test / nix build all pass).

@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: 354b6c7f25

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +72 to +73
while ((m = SUBAGENT_START_RE.exec(text)) !== null) {
opens.push({ attrs: m[1] ?? '', start: m.index, openEnd: SUBAGENT_START_RE.lastIndex });

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 Parse only top-level subagent result tags

If a subagent's returned body contains an AgentSwarm snippet or any literal <subagent ...> tag, this scan treats that embedded tag as another result row because the producer leaves body text unescaped and this loop pre-collects every opening tag in the whole output. In that case the completed/refreshed swarm card splits the real body and shows bogus/duplicate subagents; the parser needs to ignore nested/body tags or otherwise delimit only the top-level rows.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in 77f0c5e. Rewrote parseSubagents with a depth-tracking tokenizer: each <subagent ...> / </subagent> token is consumed in order; a row only registers at the outermost level, while openings/closings nested inside another row's body are treated as body text. A subagent emitting an AgentSwarm snippet no longer spawns a bogus row. Tests cover a single nested snippet and sibling rows with one of them carrying a nested snippet.

A subagent body that contains a literal `<subagent ...>` tag — for
example emitting an AgentSwarm/XML snippet — was being pre-collected as
another result row, splitting the real body and producing duplicate /
bogus subagents after refresh where the AppTask store is gone.

Rewrite parseSubagents with a depth-tracking tokenizer: scan every
`<subagent ...>` / `</subagent>` token in order, push a real row frame
only at the outermost level, and treat openings / closings while nested
inside another body as body text. Drop the now-inaccurate "literal
</subagent> without matching open" regression tests; replace with tests
that verify a balanced nested snippet stays inside the parent body and
does not register as a separate row.
@wbxl2000

wbxl2000 commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator Author

@codex New fix on top of the previous recap: 77f0c5e addresses your latest P2 on parseSwarmResult.ts — only top-level <subagent> tags count now, so a subagent's body that contains an AgentSwarm/XML snippet no longer produces a duplicate row. Tests + typecheck green. All earlier P2s still hold. CI was green before this push; re-running.

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

@wbxl2000
wbxl2000 merged commit c5e3e80 into main Jul 6, 2026
10 checks passed
@wbxl2000
wbxl2000 deleted the feat/swarm-inline-tool-card branch July 6, 2026 14:05
This was referenced Jul 6, 2026
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