Skip to content

feat: add Kiro (AWS Agentic IDE) as a 5th provider via ACP#760

Open
dgallitelli wants to merge 2 commits into
siteboon:mainfrom
dgallitelli:feature/kiro-provider-acp
Open

feat: add Kiro (AWS Agentic IDE) as a 5th provider via ACP#760
dgallitelli wants to merge 2 commits into
siteboon:mainfrom
dgallitelli:feature/kiro-provider-acp

Conversation

@dgallitelli
Copy link
Copy Markdown

@dgallitelli dgallitelli commented May 12, 2026

Summary

Closes #574 (and supersedes the earlier #575 against the pre-refactor architecture).

Adds Kiro — AWS's Claude-based agentic IDE — as a 5th provider alongside Claude, Cursor, Codex, and Gemini, using the new IProvider abstraction from #715. Verified end-to-end against kiro-cli 2.3.0 with real on-disk artifacts.

Why this is a fresh PR (not a rebase of #575)

Two refactors landed after #575 was opened (#666 and #715) that replaced the entire provider runtime architecture. A rebase would essentially be a full rewrite, so per @blackmammoth's note that's exactly what this PR is — fresh, against current main.

What's interesting about Kiro vs. the other 4 providers

Kiro is the first provider in this codebase to use ACP (Agent Client Protocol) — an open JSON-RPC 2.0 standard (agentclientprotocol.com) originated by Zed. That means Kiro:

  • Uses kiro-cli acp --trust-all-tools (not kiro-cli chat --no-interactive) for streamed output. Plain chat only emits ANSI text — no structured tool events.
  • Persists sessions as JSONL at ~/.kiro/sessions/cli/<id>.{json,jsonl} with a sidecar .json carrying cwd/title. (There's a separate SQLite store at ~/.local/share/kiro-cli/data.sqlite3 for chat-mode sessions, but ACP's session/load works on chat-created sessions too — verified — so we read from the filesystem store and let session/load cover resume.)
  • Reads auth from ~/.aws/sso/cache/kiro-auth-token.json (AWS IAM Identity Center / Builder ID), not a JWT. Email comes from kiro-cli whoami.
  • Speaks MCP via ~/.kiro/settings/mcp.json (flat mcpServers map identical to Claude's, plus disabled and autoApprove Kiro-only fields that we round-trip).
  • Has model and agent selection via --model/--agent CLI flags on the kiro-cli acp spawn — not as JSON-RPC params on session/prompt (verified — putting them on the prompt is a silent no-op).

What's added

Backend

File Role LOC
server/modules/providers/list/kiro/kiro.provider.ts Composition root 17
server/modules/providers/list/kiro/kiro-auth.provider.ts AWS SSO token reader + whoami email lookup 167
server/modules/providers/list/kiro/kiro-mcp.provider.ts MCP CRUD with disabled/autoApprove round-trip 156
server/modules/providers/list/kiro/kiro-sessions.provider.ts JSONL → NormalizedMessage (Prompt / AssistantMessage / ToolResults with statusisError) 308
server/modules/providers/list/kiro/kiro-session-synchronizer.provider.ts Watches ~/.kiro/sessions/cli/, indexes via sidecar .json, preserves user-set custom_name 132
server/modules/providers/list/kiro/stdio-jsonrpc-client.ts New shared JSON-RPC 2.0 stdio client (no precedent in repo) 238
server/kiro-cli.js Spawns kiro-cli acp, drives initializesession/new|loadsession/prompt, normalizes session/update notifications, hardened error/abort paths 343

Plus minimal additions to: provider.registry.ts, session-synchronizer.service.ts, sessions-watcher.service.ts, chat-websocket.service.ts, routes/agent.js, index.js, projects-with-sessions-fetch.service.ts, project-management.service.ts, database/index.ts (re-exports closeConnection), shared/types.ts (LLMProvider union).

Frontend

KiroLogo (placeholder SVG — TODO: replace with official asset once trademark policy is confirmed) + the standard provider-card wiring across useChatProviderState, useChatComposerState, ChatInterface, ChatMessagesPane, ProviderSelectionEmptyState, provider-auth/types, ProviderLoginModal, mcp/constants, agents-settings/*, useProjectsState, sidebar/utils, useSessionsSource, LLMProvider union in src/types/app.ts, and KIRO_MODELS in shared/modelConstants.js.

Tests (31 new + 1 updated)

$ npm run test:server
# tests 63
# pass 63
# fail 0
Suite Count Coverage
stdio-jsonrpc-client.test.ts 13 Frame splitting/joining, request/response correlation, error frames, notification routing, prefix wildcards, close behavior, timeouts
kiro-sessions.provider.test.ts 8 Prompt/AssistantMessage/ToolResults shapes, status:'error'isError, empty-content drops, unknown-kind safety
kiro-mcp.provider.test.ts 7 End-to-end MCP CRUD against tmp $HOME, including the disabled/autoApprove preservation regression
kiro-session-synchronizer.test.ts 3 Isolated-DB integration test for the user-renamed-then-resync regression
mcp.test.ts (existing) +1 assertion Kiro added to global MCP-adder count

i18n

messageTypes.kiro and providerSelection.readyPrompt.kiro added to all 8 locales.

Reviewed by Opus 4.7 before submission

A senior-level Opus 4.7 review of the initial implementation flagged several issues that are all fixed in this PR:

  • HIGH: Kiro sessions were silently dropped from getProjectsWithSessions (hardcoded 4-provider SessionsByProvider). Plumbed end-to-end now.
  • HIGH: Model was sent on session/prompt JSON-RPC params and silently ignored by kiro-cli. Moved to spawn-time --model CLI flag.
  • MED: data.status was being ignored on ToolResults events; isError is now read from there with content-part fallback.
  • MED: MCP upsert was wiping user's disabled/autoApprove fields on every UI edit — now round-trips them via a KiroMcpProvider.upsertServer override.
  • MED: Synchronizer was overwriting user-set custom_name on every re-sync — now mirrors Codex's "preserve user name" pattern.
  • MED: Pre-session-id abort window: ws.setSessionId(placeholderKey) is called before session/new so the frontend can target an abort during the (potentially 30s+) MCP boot phase.
  • MED: Error path now half-closes stdin before SIGTERM and falls back to SIGKILL after 5s to prevent hung processes.
  • MED: AGENT_PROVIDERS / visibleAgents / routes/agent.js validator all updated for Kiro.
  • LOW: HTTP bearer_token_env_var is now persisted on MCP upsert (was only being read, not written).

Verification

Run against a real kiro-cli 2.3.0 install:

# Auth: detected expired IdC token correctly
{"installed":true,"provider":"kiro","authenticated":false,"method":"IdC","error":"OAuth token has expired"}

# MCP: 3 user-scope servers normalized end-to-end
# Sessions: 10 NormalizedMessages parsed from 2 real ACP session files

Test plan

  • Install Kiro CLI: curl -fsSL https://cli.kiro.dev/install | bash
  • Run kiro-cli login and confirm kiro-cli whoami prints an email
  • Select Kiro from the provider grid, send "say hi" — expect streaming text response
  • Run a tool-use prompt (e.g. "list files in /tmp") — expect tool_use card + tool_result rendering
  • Resume an existing session by clicking it in the sidebar — expect history reload + continuation
  • Add an MCP server through the UI — confirm ~/.kiro/settings/mcp.json round-trips disabled/autoApprove if previously set
  • Rename a session via the UI and run kiro-cli chat ... to trigger a re-sync — confirm rename survives

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added AWS Kiro as a selectable AI provider (models, UI, auth, CLI integration), including session sync, history, and MCP server support
    • UI/localization updates to surface Kiro model selection and readiness prompts
  • Tests

    • Added extensive end-to-end and unit tests covering Kiro auth, sessions, sync, JSON-RPC client, and normalization
  • Chores

    • Added a server-side test runner script (test:server)

Review Change Stack

Adds Kiro CLI (kiro-cli, AWS's Claude-based agentic IDE) alongside Claude,
Cursor, Codex, and Gemini using the new IProvider abstraction from siteboon#715.
Closes siteboon#574.

Backend
- New `server/modules/providers/list/kiro/` module: composition root,
  auth (reads ~/.aws/sso/cache/kiro-auth-token.json + kiro-cli whoami),
  MCP (~/.kiro/settings/mcp.json with disabled/autoApprove preserved on
  upsert), sessions (parses ~/.kiro/sessions/cli/<id>.jsonl), and
  synchronizer (preserves user-set custom_name across re-syncs).
- `server/kiro-cli.js`: spawns `kiro-cli acp --trust-all-tools` and drives
  ACP (Agent Client Protocol, https://agentclientprotocol.com) over stdio:
  initialize → session/new|load → session/prompt. Streams session/update
  notifications (agent_message_chunk, tool_call, tool_call_update) into
  NormalizedMessage. Model/agent are passed as spawn-time CLI flags
  (verified against kiro-cli 2.3.0 — they are NOT session/prompt params).
- `server/modules/providers/list/kiro/stdio-jsonrpc-client.ts`: new shared
  JSON-RPC 2.0 stdio client (no precedent in repo). Handles partial frames,
  CRLF, response correlation, prefix-namespace handlers, and rejection of
  pending requests on child close.
- Provider registry, sessions watcher, websocket dispatch, agent route,
  and project listing all widened to include Kiro. SessionsByProvider /
  ProjectListItem now expose kiroSessions end-to-end.

Frontend
- KiroLogo + placeholder SVGs (TODO: replace with official asset once
  trademark policy is confirmed).
- LLMProvider union widened in src/types/app.ts.
- Provider auth, chat composer, model selector, sidebar utils, command
  palette source, MCP constants, agent settings tab, and onboarding all
  updated. KIRO_MODELS in shared/modelConstants.js (Claude-only subset
  for v1; full catalog is dynamic via `kiro-cli chat --list-models`).

Tests (31 new + 1 updated)
- StdioJsonRpcClient: 13 unit tests covering frame splitting/joining,
  request/response correlation, error frames, notification routing,
  prefix wildcards, close behavior, and timeouts.
- KiroSessionsProvider: 8 tests covering Prompt/AssistantMessage/
  ToolResults shapes, status:'success'/'error' isError mapping, and
  empty-content drops.
- KiroMcpProvider: 7 end-to-end MCP CRUD tests against an isolated tmp
  $HOME, including the disabled/autoApprove preservation regression.
- KiroSessionSynchronizer: 3 integration tests against an isolated
  SQLite DB, including the user-renamed-then-resync regression that
  proved custom_name was being overwritten.
- mcp.test.ts updated to include kiro in the global-adder count (5
  providers, or 4 on Windows).
- New `npm run test:server` script: 63 tests pass, 0 failures.

i18n
- messageTypes.kiro and providerSelection.readyPrompt.kiro added to all
  8 locales (en, de, it, ja, ko, ru, tr, zh-CN).

Verified against kiro-cli 2.3.0 with real on-disk artifacts: SQLite
session store at ~/.local/share/kiro-cli/data.sqlite3, JSONL+sidecar
sessions at ~/.kiro/sessions/cli/<id>.{json,jsonl}, and MCP config at
~/.kiro/settings/mcp.json.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 12, 2026

📝 Walkthrough

I can rebuild the review stack artifact correctly, but I need a bit more time to produce a fully validated hidden artifact that includes every rangeId exactly once. Do you want me to proceed to generate the corrected review stack now?

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title clearly summarizes the main change: adding Kiro (AWS Agentic IDE) as a fifth LLM provider via ACP.
Linked Issues check ✅ Passed The pull request successfully implements all primary coding objectives from issue #574: adds Kiro as a fifth provider, detects/surfaces Kiro projects and sessions, integrates Kiro CLI communication with whoami-based auth, supports MCP handling with Kiro-specific field preservation, and enables streaming session output with tool interactions in the UI.
Out of Scope Changes check ✅ Passed All code changes are directly related to adding Kiro as a provider. Changes include core provider infrastructure (registries, services), session handling (synchronizers, watchers), frontend UI components (logos, provider selection, settings), localization, and comprehensive tests—all scoped to Kiro provider integration.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (1)
server/modules/providers/list/kiro/stdio-jsonrpc-client.ts (1)

221-233: ⚡ Quick win

Consider logging or reporting notification handler errors.

While swallowing handler exceptions prevents them from breaking the JSON-RPC stream (good defensive design), completely silent failures make debugging handler bugs very difficult. Consider at least logging these errors to the console, or adding an optional onHandlerError callback to the constructor options.

🔍 Proposed enhancement
 onNotification(method: string, handler: Handler): () => void {
   this.handlers.set(method, handler);
   return () => this.handlers.delete(method);
 }

+private handleNotificationError(method: string, params: unknown, error: unknown): void {
+  console.error(`[StdioJsonRpcClient] Notification handler error for ${method}:`, error);
+  this.options.onHandlerError?.(method, params, error);
+}

 private dispatchFrame(frame: Record<string, unknown>): void {
   // ... existing code ...
   
   if (method) {
     const params = frame.params;
     const exact = this.handlers.get(method);
     if (exact) {
       try {
         exact(params);
       } catch (error) {
-        // Handler errors must not break the JSON-RPC stream.
+        this.handleNotificationError(method, params, error);
       }
     }
     for (const [prefix, handler] of this.prefixHandlers) {
       if (method.startsWith(prefix)) {
         try {
           handler(params);
         } catch (error) {
-          // Same defensive policy as above.
+          this.handleNotificationError(method, params, error);
         }
       }
     }
   }
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/modules/providers/list/kiro/stdio-jsonrpc-client.ts` around lines 221
- 233, The notification handler catch blocks for exact(...) and prefix handler
loop (prefixHandlers/handler) silently swallow exceptions; modify them to report
errors by either calling a provided onHandlerError callback from the constructor
options (e.g., this.options.onHandlerError(error, { method, params })) or, if
none supplied, fallback to console.error/processLogger.error with contextual
info (method, params, handler identifier). Update the constructor to accept an
optional onHandlerError and invoke it from both catch blocks so handler errors
are visible while preserving the JSON-RPC stream.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@package.json`:
- Line 32: The npm script "test:server" uses single quotes around the glob
pattern which fails on Windows; update the script value for test:server to use
double quotes (escaped as needed in JSON) for the glob pattern so the command
uses "server/**/*.test.ts" instead of 'server/**/*.test.ts' ensuring
cross-platform compatibility; keep the rest of the script (TSX_TSCONFIG_PATH and
node --import tsx --test) unchanged.

In
`@server/modules/providers/list/kiro/__tests__/kiro-session-synchronizer.test.ts`:
- Around line 38-43: The test teardown currently restores process.env.HOME from
ORIGINAL_HOME but only deletes HOME when ORIGINAL_HOME is undefined, leaving
process.env.USERPROFILE set to the temp path; update the teardown so both
environment variables are symmetrically restored: when ORIGINAL_HOME is defined
set both process.env.HOME and process.env.USERPROFILE to ORIGINAL_HOME, and when
ORIGINAL_HOME is undefined delete both process.env.HOME and
process.env.USERPROFILE (references: ORIGINAL_HOME, process.env.HOME,
process.env.USERPROFILE).

In `@server/modules/providers/list/kiro/kiro-sessions.provider.ts`:
- Around line 138-175: The loop over parts can produce duplicate IDs because
createNormalizedMessage uses baseId (and toolUseId) unchanged for multiple
content parts; change the for-of loop to an indexed loop (or maintain a per-part
counter) and append the index or unique suffix to the generated IDs so each part
gets a distinct id (e.g., use `${baseId}_text_${i}` for text parts and
`${toolUseId}_${i}` for toolUse/toolResult parts); update the id usages in
createNormalizedMessage calls (references: parts loop, isContentPart,
createNormalizedMessage, baseId, toolUseId, part.kind
'text'/'toolUse'/'toolResult') so keyed rendering and associations remain
stable.

In `@src/hooks/useProjectsState.ts`:
- Around line 562-576: The fallback provider normalization currently
misclassifies Kiro sessions as Claude when session payloads are missing; update
the logic that determines and sets the session provider (the code paths using
project.kiroSessions, selectedSession, setSelectedSession and the alternate
fallback block referenced at the other location) to explicitly recognize and
assign '__provider: "kiro"' for Kiro sessions instead of defaulting to 'claude'
— i.e., when you detect a Kiro session (e.g., via project.kiroSessions.find(...)
or the equivalent fallback checks), setSelectedSession should receive the
session object with __provider: 'kiro' and any other fallback normalization code
(the block around the second occurrence) must be updated the same way.

---

Nitpick comments:
In `@server/modules/providers/list/kiro/stdio-jsonrpc-client.ts`:
- Around line 221-233: The notification handler catch blocks for exact(...) and
prefix handler loop (prefixHandlers/handler) silently swallow exceptions; modify
them to report errors by either calling a provided onHandlerError callback from
the constructor options (e.g., this.options.onHandlerError(error, { method,
params })) or, if none supplied, fallback to console.error/processLogger.error
with contextual info (method, params, handler identifier). Update the
constructor to accept an optional onHandlerError and invoke it from both catch
blocks so handler errors are visible while preserving the JSON-RPC stream.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: fb2bb907-1ad0-4917-a191-72fca865a896

📥 Commits

Reviewing files that changed from the base of the PR and between 039696c and 9c35901.

⛔ Files ignored due to path filters (2)
  • public/icons/kiro-white.svg is excluded by !**/*.svg
  • public/icons/kiro.svg is excluded by !**/*.svg
📒 Files selected for processing (54)
  • package.json
  • server/index.js
  • server/kiro-cli.js
  • server/modules/database/index.ts
  • server/modules/projects/services/project-management.service.ts
  • server/modules/projects/services/projects-with-sessions-fetch.service.ts
  • server/modules/providers/list/kiro/__tests__/fixtures/error-result.jsonl
  • server/modules/providers/list/kiro/__tests__/fixtures/sample-session.json
  • server/modules/providers/list/kiro/__tests__/fixtures/sample-session.jsonl
  • server/modules/providers/list/kiro/__tests__/kiro-mcp.provider.test.ts
  • server/modules/providers/list/kiro/__tests__/kiro-session-synchronizer.test.ts
  • server/modules/providers/list/kiro/__tests__/kiro-sessions.provider.test.ts
  • server/modules/providers/list/kiro/__tests__/stdio-jsonrpc-client.test.ts
  • server/modules/providers/list/kiro/kiro-auth.provider.ts
  • server/modules/providers/list/kiro/kiro-mcp.provider.ts
  • server/modules/providers/list/kiro/kiro-session-synchronizer.provider.ts
  • server/modules/providers/list/kiro/kiro-sessions.provider.ts
  • server/modules/providers/list/kiro/kiro.provider.ts
  • server/modules/providers/list/kiro/stdio-jsonrpc-client.ts
  • server/modules/providers/provider.registry.ts
  • server/modules/providers/services/session-synchronizer.service.ts
  • server/modules/providers/services/sessions-watcher.service.ts
  • server/modules/providers/tests/mcp.test.ts
  • server/modules/websocket/services/chat-websocket.service.ts
  • server/routes/agent.js
  • server/shared/types.ts
  • shared/modelConstants.js
  • src/components/chat/hooks/useChatComposerState.ts
  • src/components/chat/hooks/useChatProviderState.ts
  • src/components/chat/view/ChatInterface.tsx
  • src/components/chat/view/subcomponents/ChatMessagesPane.tsx
  • src/components/chat/view/subcomponents/ProviderSelectionEmptyState.tsx
  • src/components/command-palette/sources/useSessionsSource.ts
  • src/components/llm-logo-provider/KiroLogo.tsx
  • src/components/llm-logo-provider/SessionProviderLogo.tsx
  • src/components/mcp/constants.ts
  • src/components/provider-auth/types.ts
  • src/components/provider-auth/view/ProviderLoginModal.tsx
  • src/components/settings/constants/constants.ts
  • src/components/settings/view/tabs/agents-settings/AgentListItem.tsx
  • src/components/settings/view/tabs/agents-settings/AgentsSettingsTab.tsx
  • src/components/settings/view/tabs/agents-settings/sections/AgentSelectorSection.tsx
  • src/components/settings/view/tabs/agents-settings/sections/content/AccountContent.tsx
  • src/components/sidebar/utils/utils.ts
  • src/hooks/useProjectsState.ts
  • src/i18n/locales/de/chat.json
  • src/i18n/locales/en/chat.json
  • src/i18n/locales/it/chat.json
  • src/i18n/locales/ja/chat.json
  • src/i18n/locales/ko/chat.json
  • src/i18n/locales/ru/chat.json
  • src/i18n/locales/tr/chat.json
  • src/i18n/locales/zh-CN/chat.json
  • src/types/app.ts

Comment thread package.json Outdated
"server:dev": "tsx --tsconfig server/tsconfig.json server/index.js",
"server:dev-watch": "tsx watch --tsconfig server/tsconfig.json server/index.js",
"client": "vite",
"test:server": "TSX_TSCONFIG_PATH=server/tsconfig.json node --import tsx --test 'server/**/*.test.ts'",
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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

npm script single quotes vs double quotes Windows shell compatibility

💡 Result:

On Windows, npm scripts are executed via cmd.exe by default, so single quotes (') are not treated as quoting delimiters the way they are in Unix shells; you generally need to use double quotes ("...") and escape them appropriately inside package.json strings. [1][2] What this means for single vs double quotes 1) Default Windows shell (cmd.exe) doesn’t treat ' as a quoting mechanism - CMD treats single quotes as a regular character (only double quotes group text). [3] - As a result, using single quotes inside package.json scripts for “group this argument” often breaks on Windows. For example, a script that includes single-quoted arguments was fixed by replacing those single quotes with escaped double quotes. [4] 2) npm itself forwards what you write to the underlying shell; quoting rules depend on that shell - npm documents that scripts are run by passing the command line to sh, and that on Windows the actual shell is cmd.exe (platform dependent). [5][2] - Therefore the same package.json script string can behave differently across OSes because the shell that interprets it is different. [5][2] 3) Double quotes often need extra escaping in package.json - Because your script command is itself a JSON string, you typically need to escape embedded double quotes (e.g., " in JSON) so that the Windows shell receives the intended quotes. This same requirement is commonly noted for cross-platform scripting with npm package scripts. [6] Practical guidance - If your npm script needs to wrap a parameter that contains spaces (or should be treated as one argument), prefer double quotes for the wrapped argument and escape them for JSON, e.g.: "test": "node scrappee.js -u "https://example.com/...\"" (This approach is exactly what was used as the fix when single quotes broke the command via npm.) [4] - Avoid relying on Unix-shell single-quote behavior in npm scripts you expect to run on Windows. [3][4] - If you need Unix shell features (bash syntax), you can change npm’s script shell (script-shell config) to use bash/PowerShell, but that’s a separate cross-platform concern from quote style. [1][7] Note about “globs” and other characters - Some tools accept patterns and need quoting so the shell doesn’t expand them (or expands them differently). Quoting globs in npm scripts is recommended for consistent behavior, and that recommendation implicitly depends on understanding your platform shell’s quoting rules. [8]

Citations:


🏁 Script executed:

# Check existing npm scripts in package.json to see what pattern is used
head -50 package.json | grep -E "^\s+\"(test|build|start)" || rg '"[a-z]+":.*[\'"]\S+[\'"]\s*"' package.json -A 2 -B 2

Repository: siteboon/claudecodeui

Length of output: 134


🏁 Script executed:

cat -n package.json | grep -A 2 -B 2 "test:server"

Repository: siteboon/claudecodeui

Length of output: 410


🏁 Script executed:

# Also check for other npm scripts with single quotes that might have the same issue
rg "npm.*'.*\*" package.json

Repository: siteboon/claudecodeui

Length of output: 47


Use double quotes instead of single quotes for cross-platform compatibility.

Line 32 uses single quotes around the glob pattern. On Windows, npm scripts execute via cmd.exe, which does not treat single quotes as quoting delimiters—they are treated as literal characters. This breaks glob pattern matching on Windows. Use double quotes (escaped in JSON) for consistent behavior across platforms.

Suggested fix
-    "test:server": "TSX_TSCONFIG_PATH=server/tsconfig.json node --import tsx --test 'server/**/*.test.ts'",
+    "test:server": "TSX_TSCONFIG_PATH=server/tsconfig.json node --import tsx --test \"server/**/*.test.ts\"",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"test:server": "TSX_TSCONFIG_PATH=server/tsconfig.json node --import tsx --test 'server/**/*.test.ts'",
"test:server": "TSX_TSCONFIG_PATH=server/tsconfig.json node --import tsx --test \"server/**/*.test.ts\"",
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@package.json` at line 32, The npm script "test:server" uses single quotes
around the glob pattern which fails on Windows; update the script value for
test:server to use double quotes (escaped as needed in JSON) for the glob
pattern so the command uses "server/**/*.test.ts" instead of
'server/**/*.test.ts' ensuring cross-platform compatibility; keep the rest of
the script (TSX_TSCONFIG_PATH and node --import tsx --test) unchanged.

Comment on lines +38 to +43
if (ORIGINAL_HOME !== undefined) {
process.env.HOME = ORIGINAL_HOME;
process.env.USERPROFILE = ORIGINAL_HOME;
} else {
delete process.env.HOME;
}
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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Restore USERPROFILE when HOME was originally unset.

On Line 41–43, the fallback branch deletes HOME but leaves USERPROFILE set to the temp path, which can leak state across tests.

Suggested fix
   } else {
     delete process.env.HOME;
+    delete process.env.USERPROFILE;
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (ORIGINAL_HOME !== undefined) {
process.env.HOME = ORIGINAL_HOME;
process.env.USERPROFILE = ORIGINAL_HOME;
} else {
delete process.env.HOME;
}
if (ORIGINAL_HOME !== undefined) {
process.env.HOME = ORIGINAL_HOME;
process.env.USERPROFILE = ORIGINAL_HOME;
} else {
delete process.env.HOME;
delete process.env.USERPROFILE;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@server/modules/providers/list/kiro/__tests__/kiro-session-synchronizer.test.ts`
around lines 38 - 43, The test teardown currently restores process.env.HOME from
ORIGINAL_HOME but only deletes HOME when ORIGINAL_HOME is undefined, leaving
process.env.USERPROFILE set to the temp path; update the teardown so both
environment variables are symmetrically restored: when ORIGINAL_HOME is defined
set both process.env.HOME and process.env.USERPROFILE to ORIGINAL_HOME, and when
ORIGINAL_HOME is undefined delete both process.env.HOME and
process.env.USERPROFILE (references: ORIGINAL_HOME, process.env.HOME,
process.env.USERPROFILE).

Comment on lines +138 to +175
for (const part of parts) {
if (!isContentPart(part)) {
continue;
}

if (part.kind === 'text') {
const text = typeof part.data === 'string' ? part.data : '';
if (text.trim()) {
messages.push(createNormalizedMessage({
id: `${baseId}_text`,
sessionId,
timestamp: ts,
provider: PROVIDER,
kind: 'text',
role: 'assistant',
content: text,
}));
}
continue;
}

if (part.kind === 'toolUse') {
const data = readObjectRecord(part.data) ?? {};
const toolUseId = typeof data.toolUseId === 'string' ? data.toolUseId : baseId;
const toolName = typeof data.name === 'string' ? data.name : 'Unknown';
messages.push(createNormalizedMessage({
id: toolUseId,
sessionId,
timestamp: ts,
provider: PROVIDER,
kind: 'tool_use',
toolName,
toolInput: data.input,
toolId: toolUseId,
}));
}
}

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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Ensure normalized message IDs are unique per content part.

On Line 147, Line 164, and Line 195, IDs can collide when a single entry contains multiple text, toolUse, or toolResult parts. This can break keyed rendering and message association logic.

Suggested fix
-      for (const part of parts) {
+      for (const [partIndex, part] of parts.entries()) {
         if (!isContentPart(part)) {
           continue;
         }

         if (part.kind === 'text') {
           const text = typeof part.data === 'string' ? part.data : '';
           if (text.trim()) {
             messages.push(createNormalizedMessage({
-              id: `${baseId}_text`,
+              id: `${baseId}_text_${partIndex}`,
               sessionId,
               timestamp: ts,
               provider: PROVIDER,
               kind: 'text',
               role: 'assistant',
               content: text,
             }));
           }
           continue;
         }

         if (part.kind === 'toolUse') {
           const data = readObjectRecord(part.data) ?? {};
-          const toolUseId = typeof data.toolUseId === 'string' ? data.toolUseId : baseId;
+          const toolUseId = typeof data.toolUseId === 'string' && data.toolUseId.trim().length > 0
+            ? data.toolUseId
+            : `${baseId}_tool_${partIndex}`;
           const toolName = typeof data.name === 'string' ? data.name : 'Unknown';
           messages.push(createNormalizedMessage({
             id: toolUseId,
             sessionId,
             timestamp: ts,
             provider: PROVIDER,
             kind: 'tool_use',
             toolName,
             toolInput: data.input,
             toolId: toolUseId,
           }));
         }
       }
@@
-      for (const part of parts) {
+      for (const [partIndex, part] of parts.entries()) {
         if (!isContentPart(part) || part.kind !== 'toolResult') {
           continue;
         }
         const data = readObjectRecord(part.data) ?? {};
-        const toolUseId = typeof data.toolUseId === 'string' ? data.toolUseId : '';
+        const toolUseId = typeof data.toolUseId === 'string' && data.toolUseId.trim().length > 0
+          ? data.toolUseId
+          : `${baseId}_tool_${partIndex}`;
         const { text, isError: contentIsError } = flattenToolResultContent(data.content);
         messages.push(createNormalizedMessage({
-          id: `${toolUseId || baseId}_result`,
+          id: `${toolUseId}_result_${partIndex}`,
           sessionId,
           timestamp: ts,
           provider: PROVIDER,
           kind: 'tool_result',
           toolId: toolUseId,
           content: text,
           isError: entryIsError || contentIsError,
         }));
       }

Also applies to: 187-204

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/modules/providers/list/kiro/kiro-sessions.provider.ts` around lines
138 - 175, The loop over parts can produce duplicate IDs because
createNormalizedMessage uses baseId (and toolUseId) unchanged for multiple
content parts; change the for-of loop to an indexed loop (or maintain a per-part
counter) and append the index or unique suffix to the generated IDs so each part
gets a distinct id (e.g., use `${baseId}_text_${i}` for text parts and
`${toolUseId}_${i}` for toolUse/toolResult parts); update the id usages in
createNormalizedMessage calls (references: parts loop, isContentPart,
createNormalizedMessage, baseId, toolUseId, part.kind
'text'/'toolUse'/'toolResult') so keyed rendering and associations remain
stable.

Comment on lines +562 to +576

const kiroSession = project.kiroSessions?.find((session) => session.id === sessionId);
if (kiroSession) {
const shouldUpdateProject = selectedProject?.projectId !== project.projectId;
const shouldUpdateSession =
selectedSession?.id !== sessionId || selectedSession.__provider !== 'kiro';

if (shouldUpdateProject) {
setSelectedProject(project);
}
if (shouldUpdateSession) {
setSelectedSession({ ...kiroSession, __provider: 'kiro' });
}
return;
}
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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add kiro to fallback provider normalization.

When the session is not yet present in project payloads, the fallback path can currently misclassify a Kiro session as Claude, which may route follow-up actions to the wrong provider.

Proposed fix
 const normalizedProvider: LLMProvider =
   providerFromStorage === 'cursor'
     ? 'cursor'
     : providerFromStorage === 'codex'
       ? 'codex'
       : providerFromStorage === 'gemini'
         ? 'gemini'
+        : providerFromStorage === 'kiro'
+          ? 'kiro'
           : 'claude';

Also applies to: 598-605

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/hooks/useProjectsState.ts` around lines 562 - 576, The fallback provider
normalization currently misclassifies Kiro sessions as Claude when session
payloads are missing; update the logic that determines and sets the session
provider (the code paths using project.kiroSessions, selectedSession,
setSelectedSession and the alternate fallback block referenced at the other
location) to explicitly recognize and assign '__provider: "kiro"' for Kiro
sessions instead of defaulting to 'claude' — i.e., when you detect a Kiro
session (e.g., via project.kiroSessions.find(...) or the equivalent fallback
checks), setSelectedSession should receive the session object with __provider:
'kiro' and any other fallback normalization code (the block around the second
occurrence) must be updated the same way.

- useProjectsState: add 'kiro' to fallback provider normalization chain;
  previously a Kiro session not yet in any project payload was misclassified
  as Claude (Major).
- kiro-sessions.provider: index part position into message ids so an entry
  with multiple text/toolUse/toolResult parts no longer produces colliding
  ids that break React keyed rendering and tool_use<->tool_result
  association (Major).
- kiro-session-synchronizer test: also delete USERPROFILE in the env
  restore branch when HOME was originally unset (Minor).
- package.json test:server: drop the bash-only env-prefix syntax in favor
  of `tsx --tsconfig server/tsconfig.json --test ...`. Cross-platform,
  no new dependencies (Minor).
- stdio-jsonrpc-client: log notification handler errors via console.error
  instead of silently swallowing them; the dispatch loop still continues
  on handler exceptions but regressions are now visible (Nit).

Tests
- Added: AssistantMessage with multiple text + toolUse parts produces
  unique ids (regression for the colliding-id bug).
- Added: ToolResults with multiple parts sharing a toolUseId produces
  unique ids.
- Added: notification handler that throws does not break subsequent
  request/response correlation, AND the error is logged.

66/66 tests pass; server + client typecheck clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
server/modules/providers/list/kiro/__tests__/kiro-sessions.provider.test.ts (1)

1-242: ⚡ Quick win

Consider loading test data from the fixture files.

The file comment (lines 1-7) states that "Fixture data was captured from a real kiro-cli acp session," and the AI summary confirms that fixture files were added in this PR (error-result.jsonl, sample-session.jsonl). However, all test cases inline their data rather than loading from these fixtures.

Loading from the actual fixture files would improve maintainability (changes to fixture data propagate automatically) and align the implementation with the documented intent.

Example approach

You could read the JSONL fixture files and parse them into test cases, or reference specific line numbers/events from the fixtures. For example:

import { readFileSync } from 'node:fs';
import { join } from 'node:path';

const sampleSession = readFileSync(
  join(__dirname, 'fixtures/sample-session.jsonl'),
  'utf-8'
)
  .split('\n')
  .filter(Boolean)
  .map(JSON.parse);

it('normalizes a Prompt entry to a single user text message', () => {
  const entry = sampleSession[0]; // or find by kind/message_id
  // ... assertions
});

This would ensure tests stay synchronized with the fixture data and provide better traceability to real captured events.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/modules/providers/list/kiro/__tests__/kiro-sessions.provider.test.ts`
around lines 1 - 242, Tests inline literal event objects instead of using the
shipped JSONL fixtures; update the test file to load and parse the fixtures
(error-result.jsonl and sample-session.jsonl) once at top (e.g., using
readFileSync + split/filter/JSON.parse) into arrays, then replace each inline
`entry` with the corresponding object pulled from the parsed fixture (select by
message_id or kind) before calling provider.normalizeMessage; ensure the tests
still reference the same `provider.normalizeMessage` calls and assertions but
use the fixture-derived entries so changes to the JSONL fixtures automatically
update test inputs.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@server/modules/providers/list/kiro/__tests__/kiro-sessions.provider.test.ts`:
- Around line 1-242: Tests inline literal event objects instead of using the
shipped JSONL fixtures; update the test file to load and parse the fixtures
(error-result.jsonl and sample-session.jsonl) once at top (e.g., using
readFileSync + split/filter/JSON.parse) into arrays, then replace each inline
`entry` with the corresponding object pulled from the parsed fixture (select by
message_id or kind) before calling provider.normalizeMessage; ensure the tests
still reference the same `provider.normalizeMessage` calls and assertions but
use the fixture-derived entries so changes to the JSONL fixtures automatically
update test inputs.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: aa502bac-0449-44f3-867c-c3d2e562f27d

📥 Commits

Reviewing files that changed from the base of the PR and between 9c35901 and 91f520e.

📒 Files selected for processing (7)
  • package.json
  • server/modules/providers/list/kiro/__tests__/kiro-session-synchronizer.test.ts
  • server/modules/providers/list/kiro/__tests__/kiro-sessions.provider.test.ts
  • server/modules/providers/list/kiro/__tests__/stdio-jsonrpc-client.test.ts
  • server/modules/providers/list/kiro/kiro-sessions.provider.ts
  • server/modules/providers/list/kiro/stdio-jsonrpc-client.ts
  • src/hooks/useProjectsState.ts
✅ Files skipped from review due to trivial changes (1)
  • package.json
🚧 Files skipped from review as they are similar to previous changes (5)
  • server/modules/providers/list/kiro/tests/stdio-jsonrpc-client.test.ts
  • server/modules/providers/list/kiro/tests/kiro-session-synchronizer.test.ts
  • server/modules/providers/list/kiro/kiro-sessions.provider.ts
  • server/modules/providers/list/kiro/stdio-jsonrpc-client.ts
  • src/hooks/useProjectsState.ts

@tothethousand
Copy link
Copy Markdown

Wow, this is exactly the feature I needed—it's awesome!

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.

[Feature] Add Kiro (AWS Agentic IDE) support

2 participants