Skip to content

fix: extract action params from standalone XML blocks in comma-separated format#6692

Merged
HaruHunab1320 merged 1 commit intodevelopfrom
fix/comma-separated-action-params
Mar 27, 2026
Merged

fix: extract action params from standalone XML blocks in comma-separated format#6692
HaruHunab1320 merged 1 commit intodevelopfrom
fix/comma-separated-action-params

Conversation

@HaruHunab1320
Copy link
Copy Markdown
Contributor

@HaruHunab1320 HaruHunab1320 commented Mar 27, 2026

Summary

  • When the LLM outputs actions as <actions>REPLY,START_CODING_TASK</actions> (comma-separated), the message service extracted action names but dropped all parameters
  • The LLM outputs params in standalone sibling blocks like <START_CODING_TASK><repo>...</repo></START_CODING_TASK> — the XML parser puts these as top-level keys on parsedXml, but the comma-split path never collected them into parsedXml.params
  • This caused processActions to call handlers with empty options.parameters, breaking any action that relies on params (e.g., coding agent spawning where task/agents/repo are needed)

Fix

After comma-splitting action names, scan parsedXml for top-level keys matching those names. If found and they contain XML children, assemble them into parsedXml.params in the legacy flat format (<ACTION_NAME>...</ACTION_NAME>) that parseActionParams already consumes.

Guards:

  • Only populates when parsedXml.params is not already set (structured <action> blocks take priority)
  • Skips reserved keys (actions, thought, text, simple)
  • Only matches string values containing < (actual XML, not plain text)

Test plan

  • comma-separated-action-params.test.ts — 7 tests covering source verification + parseActionParams integration with the flat format

🤖 Generated with Claude Code

Greptile Summary

This PR fixes a bug where action parameters were silently dropped when the LLM used the comma-separated <actions>REPLY,START_CODING_TASK</actions> format instead of the structured <action> block format. The fix scans parsedXml for top-level keys matching the comma-split action names and assembles them into the parsedXml.params string that parseActionParams already knows how to consume, mirroring what the structured-action path already did.\n\nChanges:\n- message.ts – After comma-splitting action names, iterates parsedXml for matching top-level keys (skipping reserved ones and non-XML values), wraps them into the legacy flat <ACTION_NAME>…</ACTION_NAME> format, and assigns the result to parsedXml.params (only when params is not already populated). Also cleans up excessive indentation in the TTS block.\n- New test file – 7 tests: 4 source-inspection assertions (variable names, patterns) and 3 proper parseActionParams integration tests covering single-action, multi-action, and empty-input cases.\n\nObservations:\n- The core logic is correct: parsedXml[matchingKey] holds inner XML content (the XML parser strips the outer wrapper), so re-wrapping with <UPPER_NAME> before joining produces exactly the legacy flat format expected by parseActionParams.\n- The !parsedXml.params || parsedXml.params === \"\" guard correctly prevents overwriting params already populated by the structured-action path.\n- The reserved-key exclusions (actions, thought, text, simple) use case-sensitive string comparisons against matchingKey, which could miss uppercase/mixed-case variants emitted by some XML parsers.\n- The source-inspection tests in the first describe block are brittle: they assert on internal variable names and will break on any rename, even safe ones.

Confidence Score: 5/5

Safe to merge; the fix correctly addresses the parameter-loss bug and all remaining findings are P2 style/robustness suggestions.

The functional change is correct and well-guarded: it only activates when params are absent, skips reserved keys, validates for XML content, and the assembled string is in the exact format that the existing parseActionParams already handles. The two P2 findings (brittle source-inspection tests and case-sensitive reserved-key guards) are non-blocking quality improvements. No P0 or P1 issues found.

packages/typescript/src/tests/comma-separated-action-params.test.ts — the source-inspection test block should ideally be replaced with behavioural tests.

Important Files Changed

Filename Overview
packages/typescript/src/services/message.ts Adds standalone XML-block param extraction for comma-split action format; also cleans up TTS indentation. Logic is sound but reserved-key comparisons are case-sensitive.
packages/typescript/src/tests/comma-separated-action-params.test.ts New regression tests covering the fix; the first describe block uses brittle source-inspection assertions (variable name checks) rather than behavioural tests.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[LLM response parsed into parsedXml] --> B{parsedXml.actions type?}
    B -- "string containing action" --> C[Structured path: extract action entries and inline params]
    C --> D{actionEntries found?}
    D -- yes --> E[Build inlineParamsXml string if parsedXml.params is empty]
    E --> F[Return action names list]
    D -- no --> G[Fall through to comma-split path]
    B -- "plain comma-separated string" --> G
    G --> H[Split by comma → commaSplitActions]
    H --> I{parsedXml.params empty?}
    I -- yes --> J[For each action name: find matching top-level key in parsedXml]
    J --> K{Key found and not reserved and value contains '<'?}
    K -- yes --> L[Push ACTION_NAME inner content to standaloneParamsFragments]
    K -- no --> M[Skip]
    L --> N{Any fragments?}
    N -- yes --> O[Set parsedXml.params = joined fragments]
    O --> P[Return commaSplitActions]
    N -- no --> P
    I -- no --> P
    B -- "array" --> Q[Return parsedXml.actions directly]
    F --> R[responseContent = spread parsedXml + overrides]
    P --> R
    Q --> R
    R --> S[parseActionParams reads responseContent.params → Map of action to params]
Loading

Reviews (1): Last reviewed commit: "fix: extract action params from standalo..." | Re-trigger Greptile

Greptile also left 2 inline comments on this PR.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 27, 2026

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c60191f8-c43a-4c5e-b676-b9fbd6d1137c

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/comma-separated-action-params

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@claude
Copy link
Copy Markdown
Contributor

claude bot commented Mar 27, 2026

Claude finished @HaruHunab1320's task in 1m 5s —— View job


PR Review: fix/comma-separated-action-params

  • Read CLAUDE.md for repo guidelines
  • Review changed files (message.ts, test file)
  • Check for security issues
  • Verify test coverage
  • Provide feedback

✅ Overall: LGTM

The fix correctly addresses the issue where action params were dropped when the LLM outputs actions in comma-separated format with standalone XML blocks.


Changes Summary

packages/typescript/src/services/message.ts

  • Extracts params from standalone action blocks (<ACTION_NAME><params>...</ACTION_NAME>) when using comma-separated format
  • Guards against overwriting existing params
  • Filters reserved keys (actions, thought, text, simple)

packages/typescript/src/tests/comma-separated-action-params.test.ts

  • ✅ Uses vitest as required
  • ✅ Tests source code behavior verification
  • ✅ Tests parseActionParams integration with legacy flat format
  • ✅ Tests edge cases (empty input, multiple actions)

Feedback

⚠️ IMPORTANT: Unrelated indentation changes at lines 332-370
The diff includes whitespace/indentation cleanup in the TTS section that's unrelated to the fix. Consider separating this into its own commit or PR for cleaner history.

💡 SUGGESTION: Source code string matching tests are brittle
Tests like expect(messageServiceSource).toContain("standaloneParamsFragments") will break if variable names are refactored. Consider adding integration tests that actually invoke the message service with mock LLM output. However, I understand these serve as documentation for expected behavior.

💡 SUGGESTION: Consider extracting reserved keys to a constant

const RESERVED_XML_KEYS = ["actions", "thought", "text", "simple"] as const;
// Then use: !RESERVED_XML_KEYS.includes(matchingKey as any)

This improves maintainability if more reserved keys are added later.


Security

✅ No security issues found - input is already parsed XML from LLM output

Type Safety

✅ Proper type checking with typeof parsedXml[matchingKey] === "string"

Test Coverage

✅ 7 tests covering the fix, using vitest


Verdict: Ready to merge after considering the unrelated indentation changes.

Comment on lines +27 to +53
describe("comma-separated action params extraction (message service)", () => {
it("scans parsedXml for standalone action blocks in the comma-split path", () => {
// The fix must look for top-level parsedXml keys matching action names
expect(messageServiceSource).toContain("standaloneParamsFragments");
expect(messageServiceSource).toContain("commaSplitActions");
});

it("skips reserved keys (actions, thought, text, simple)", () => {
expect(messageServiceSource).toMatch(/matchingKey\s*!==\s*"actions"/);
expect(messageServiceSource).toMatch(/matchingKey\s*!==\s*"thought"/);
expect(messageServiceSource).toMatch(/matchingKey\s*!==\s*"text"/);
});

it("only populates params when not already set", () => {
// Guard: don't overwrite explicit params from structured <action> blocks
expect(messageServiceSource).toMatch(
/!parsedXml\.params\s*\|\|\s*parsedXml\.params\s*===\s*""/,
);
});

it("assembles params in legacy flat format for parseActionParams", () => {
// The assembled string wraps action content: <UPPER_NAME>content</UPPER_NAME>
expect(messageServiceSource).toContain("standaloneParamsFragments.push");
expect(messageServiceSource).toContain(
"parsedXml.params = standaloneParamsFragments",
);
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Source-inspection tests are brittle

The first describe block reads message.ts as a raw string and asserts on internal variable names like "standaloneParamsFragments" and "commaSplitActions". These tests pass only as long as those exact identifiers remain unchanged. Any safe rename/refactor of those variables would break the tests without changing any observable behaviour.

A more robust approach is to test the observable output of the normalised-action path directly by invoking a thin helper (or temporarily exporting the extraction logic) and asserting on the returned action list and resulting parsedXml.params value. That way the tests survive any internal refactoring and also catch regressions that the source-scan approach cannot (e.g. the logic running but producing the wrong output).

Comment on lines +1681 to +1692
if (
matchingKey &&
matchingKey !== "actions" &&
matchingKey !== "thought" &&
matchingKey !== "text" &&
matchingKey !== "simple" &&
typeof parsedXml[matchingKey] === "string" &&
(parsedXml[matchingKey] as string).includes("<")
) {
standaloneParamsFragments.push(
`<${upperName}>${parsedXml[matchingKey]}</${upperName}>`,
);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Reserved-key checks are case-sensitive; consider normalising

matchingKey comes directly from Object.keys(parsedXml), which preserves whatever casing the XML parser emits. If the LLM ever outputs <THOUGHT>, <ACTIONS>, or <TEXT> in uppercase (or mixed case), the guards below will not fire:

matchingKey !== "actions"   // won't catch "ACTIONS" or "Actions"
matchingKey !== "thought"   // won't catch "THOUGHT" or "Thought"
matchingKey !== "text"      // won't catch "TEXT"
matchingKey !== "simple"    // won't catch "SIMPLE"

Since the look-up itself already uses toUpperCase() for matching, applying .toLowerCase() to matchingKey in the guards would make the filtering consistent:

Suggested change
if (
matchingKey &&
matchingKey !== "actions" &&
matchingKey !== "thought" &&
matchingKey !== "text" &&
matchingKey !== "simple" &&
typeof parsedXml[matchingKey] === "string" &&
(parsedXml[matchingKey] as string).includes("<")
) {
standaloneParamsFragments.push(
`<${upperName}>${parsedXml[matchingKey]}</${upperName}>`,
);
if (
matchingKey &&
matchingKey.toLowerCase() !== "actions" &&
matchingKey.toLowerCase() !== "thought" &&
matchingKey.toLowerCase() !== "text" &&
matchingKey.toLowerCase() !== "simple" &&
typeof parsedXml[matchingKey] === "string" &&
(parsedXml[matchingKey] as string).includes("<")
) {

@HaruHunab1320 HaruHunab1320 force-pushed the fix/comma-separated-action-params branch from 268170e to 7141b4f Compare March 27, 2026 16:34
@claude
Copy link
Copy Markdown
Contributor

claude bot commented Mar 27, 2026

Claude finished @HaruHunab1320's task in 1m 9s —— View job


PR Review: fix/comma-separated-action-params

  • Read CLAUDE.md for repo guidelines
  • Review changed files (message.ts, test file)
  • Check for security issues
  • Verify test coverage
  • Provide feedback

✅ LGTM - Safe to merge

The fix correctly addresses the parameter-loss bug when LLM outputs actions in comma-separated format with standalone XML blocks.


Changes Summary

packages/typescript/src/services/message.ts

  • Adds RESERVED_XML_KEYS Set with case-insensitive lookup (addresses Greptile's P2)
  • Scans parsedXml for top-level keys matching comma-split action names
  • Assembles params into legacy flat format for parseActionParams
  • Includes unrelated TTS indentation cleanup (lines 344-380)

packages/typescript/src/tests/comma-separated-action-params.test.ts

  • ✅ Uses vitest as required
  • ✅ Tests parseActionParams integration with single/multi-action formats
  • ✅ Tests edge cases (empty input, null, undefined)

Feedback

💡 SUGGESTION: Source-inspection tests are brittle
The first describe block reads message.ts as raw text and asserts on variable names like "standaloneParamsFragments". These will break on any safe refactor. Consider replacing with behavioral tests that invoke the actual code path.

packages/typescript/src/__tests__/comma-separated-action-params.test.ts:28-52

💡 SUGGESTION: Separate unrelated changes
The TTS indentation fix (lines 344-380) is unrelated to the param extraction fix. Consider keeping atomic commits for cleaner git history.


Verification

Check Status
Security ✅ No issues - input is parsed XML from LLM
Tests exist ✅ 7 tests using vitest
TypeScript types ✅ Proper type checking, no any
Uses @elizaos/core imports ✅ N/A for this change
Follows existing patterns ✅ Mirrors structured-action path

Verdict: The core logic is correct and well-guarded. The P2 findings from Greptile (brittle tests, case-sensitive guards) have been addressed. Ready to merge.

…ted format

When the LLM outputs actions as a comma-separated list
(`<actions>REPLY,START_CODING_TASK</actions>`), the parser extracted
action names but dropped their parameters. The LLM also outputs
standalone blocks like `<START_CODING_TASK><repo>...</repo></START_CODING_TASK>`
as sibling XML elements — the XML parser puts these as top-level keys
on parsedXml, but the message service never collected them into
parsedXml.params for downstream consumption by parseActionParams.

This caused action handlers to receive empty parameters, breaking
features like coding agent swarm spawning where task/agents/repo
params are required for coordinator registration.

The fix scans parsedXml for top-level keys matching comma-split action
names and assembles them into the legacy flat params format that
parseActionParams already handles.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@HaruHunab1320 HaruHunab1320 force-pushed the fix/comma-separated-action-params branch from 7141b4f to 48f5650 Compare March 27, 2026 16:37
@claude
Copy link
Copy Markdown
Contributor

claude bot commented Mar 27, 2026

Claude finished @HaruHunab1320's task in 58s —— View job


PR Review: fix/comma-separated-action-params

  • Read CLAUDE.md for repo guidelines
  • Review changed files and previous review comments
  • Check for security issues, test coverage, and code quality
  • Provide feedback

✅ LGTM - Approved

Previous review feedback has been addressed. The fix correctly solves the parameter-loss bug.


Changes Summary

packages/typescript/src/services/message.ts

  • Adds extractStandaloneActionParams() function (exported, testable)
  • Exports RESERVED_XML_KEYS Set with case-insensitive lookup
  • Assembles standalone action blocks into legacy flat params format
  • Unrelated TTS indentation cleanup (cosmetic)

packages/typescript/src/tests/comma-separated-action-params.test.ts

  • ✅ Uses vitest
  • ✅ Behavioral tests (not source-inspection)
  • ✅ Tests extractStandaloneActionParams directly
  • ✅ Integration tests with parseActionParams
  • ✅ Edge cases covered (empty input, multiple actions, reserved keys)

Previous Review Issues - RESOLVED

Issue Status
Brittle source-inspection tests ✅ Fixed - now behavioral
Case-sensitive reserved-key guards ✅ Fixed - uses .toLowerCase()

Verification

Check Status
Security ✅ No issues
Tests exist ✅ 9 tests covering the fix
TypeScript types ✅ No any
Uses vitest
Follows existing patterns

💡 Minor: The TTS indentation cleanup (lines 377-420) is unrelated to the param extraction fix. Consider separate commits for cleaner history, but this is non-blocking.

@HaruHunab1320 HaruHunab1320 merged commit 9c842b0 into develop Mar 27, 2026
4 checks passed
@HaruHunab1320 HaruHunab1320 deleted the fix/comma-separated-action-params branch March 27, 2026 16:38
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