Skip to content

feat: bring OpenCode adapter to feature parity with other adapters#16

Merged
aaditagrawal merged 5 commits intomainfrom
feat/opencode-adapter-parity
Mar 16, 2026
Merged

feat: bring OpenCode adapter to feature parity with other adapters#16
aaditagrawal merged 5 commits intomainfrom
feat/opencode-adapter-parity

Conversation

@aaditagrawal
Copy link
Owner

@aaditagrawal aaditagrawal commented Mar 16, 2026

Summary

  • Emit session.configured and session.exited lifecycle events for parity with Claude Code, Copilot, and Codex adapters
  • Forward session.diff as turn.diff.updated with unified diff conversion, enabling file change tracking in the work log
  • Handle 6 new SSE event types: session.compacted, session.updated, vcs.branch.updated, file.edited, command.executed, message.part.removed
  • Enrich existing events with todo priority prefixes, isRetryable on API errors, permission title/pattern metadata
  • Implement rollbackThread via OpenCode's session.revert API (previously threw "not implemented")
  • Extract opencode/ module files from monolithic opencodeServerManager.ts
  • Fix flaky OpenCodeAdapter stream forwarding test (emit-before-consume eliminates fiber scheduling race)
  • Add error class labels in ProviderRuntimeIngestion for better error display ("Provider error", "Connection error", etc.)

Test plan

  • bun typecheck — 7/7 packages pass clean
  • bun test apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.test.ts — 24/24 pass
  • bun test apps/server/src/provider/Layers/OpenCodeAdapter.test.ts — 3/3 pass (previously 1 flaky timeout)
  • Manual: connect to an OpenCode session, verify new events appear in the work log
  • Manual: trigger an error (invalid API key), verify descriptive message shows instead of "Runtime Error"
  • Manual: make file changes during a turn, verify diff summary appears

Summary by CodeRabbit

  • New Features

    • Added full OpenCode and Kilo provider integrations: rich session/event handling, model discovery, server lifecycle management, and thread rollback by turns.
    • New shared utilities and type surfaces for parsing, auth, diffs, tool/plan mapping, and standardized runtime events.
    • Unified error utilities providing classification, human-readable labels, retry hints, and formatted messages.
  • Bug Fixes / Reliability

    • Centralized event dispatch and unified server lifecycle for more consistent state, error handling, and startup/reuse flows.
  • Tests

    • Simplified adapter tests to remove a race condition by awaiting stream heads directly.

- Emit session.configured after session creation and session.exited on
  stop/stream death for lifecycle parity with Claude Code and Copilot
- Forward session.diff as turn.diff.updated with unified diff conversion
- Handle 6 new SSE event types: session.compacted, session.updated,
  vcs.branch.updated, file.edited, command.executed, message.part.removed
- Enrich existing events: todo priority prefixes, isRetryable on API errors,
  permission title/pattern metadata, question multiple/custom fields
- Implement rollbackThread via session.revert API with message ID tracking
- Extract opencode/ module files from opencodeServerManager.ts
- Fix flaky OpenCodeAdapter stream forwarding test (race condition)
- Improve runtime error display with error class labels
@coderabbitai
Copy link

coderabbitai bot commented Mar 16, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds complete provider subsystems for OpenCode and Kilo: new types, utilities, centralized SSE event dispatchers, error classification/formatting, server lifecycle orchestration (probe/spawn/create client/ensure), refactored server managers with rollback and lifecycle events, and minor test consumption fixes.

Changes

Cohort / File(s) Summary
Types
apps/server/src/opencode/types.ts, apps/server/src/kilo/types.ts
Introduce large, provider-specific type systems and constants covering provider/session options, runtime events, SDK client shapes, model discovery, event unions, session context, and shared server state.
Event Handling
apps/server/src/opencode/eventHandlers.ts, apps/server/src/kilo/eventHandlers.ts
New centralized SSE dispatchers that validate scope, update session/turn/permission/question/tool state, classify errors, build runtime payloads, and emit high-level runtime events.
Error utilities
apps/server/src/opencode/errors.ts, apps/server/src/kilo/errors.ts
Centralized error classification, human-readable labels, retryability detection, and composed session error message builders.
Utilities
apps/server/src/opencode/utils.ts, apps/server/src/kilo/utils.ts
Utility helpers: ID/timestamp generators, auth header builder, model parsing/discovery, permission mapping, tool-state formatting, server URL parsing, SDK response normalization, diff/todo helpers.
Server lifecycle
apps/server/src/opencode/serverLifecycle.ts, apps/server/src/kilo/serverLifecycle.ts
Probe, dynamic SDK client creation, spawn-or-connect startup flow with output parsing, timeouts, caching and ensureServer orchestration for provider servers.
Server manager refactors
apps/server/src/opencodeServerManager.ts, apps/server/src/kiloServerManager.ts
Managers refactored to use lifecycle/utils/event handlers; emit session.configured/session.exited events, add rollbackThread(threadId, numTurns) and messageIds tracking, and standardize RPC calls via readJsonData.
Barrel exports
apps/server/src/opencode/index.ts, apps/server/src/kilo/index.ts
New index files re-exporting types, errors, utils, eventHandlers, and serverLifecycle for each provider.
Orchestration update
apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts
Adds runtime error-class → label mapping and updates runtime.error activity summary/detail to include the label and expanded detail.
Tests
apps/server/src/provider/Layers/OpenCodeAdapter.test.ts, apps/server/src/provider/Layers/KiloAdapter.test.ts
Simplified stream tests: removed Fiber fork/join; directly await stream head to avoid race conditions.
New modules / managers
apps/server/src/opencode/errors.ts, apps/server/src/kilo/errors.ts (new), apps/server/src/opencode/eventHandlers.ts, apps/server/src/kilo/eventHandlers.ts (new), apps/server/src/opencode/serverLifecycle.ts, apps/server/src/kilo/serverLifecycle.ts (new)
Introduce multiple new modules implementing core provider logic and wiring used by refactored managers.

Sequence Diagram(s)

sequenceDiagram
    participant Provider as Provider Process / SDK
    participant Lifecycle as ServerLifecycle
    participant Handler as EventHandler
    participant Context as SessionContext
    participant Emitter as RuntimeEmitter
    participant Client as OpencodeClient

    Note over Lifecycle,Provider: startup / probe / create client
    Lifecycle->>Provider: probe / spawn or connect
    Lifecycle->>Client: createClient()
    Provider->>Handler: SSE event (session.*, message.part.*, permission.*, error, ...)
    Handler->>Handler: validate providerSessionId
    Handler->>Context: update session/turn/parts/permissions/questions/lastError
    Handler->>Emitter: emit runtime event (eventId, provider, threadId, timestamps, payload)
    Handler->>Client: call RPC as needed (prompt, reply, abort)
    Client-->>Handler: RPC response
    Handler->>Context: record retry hints / messageIds (for rollback)
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

Suggested labels

size:XL

Poem

🐇 I hop through events and stitch each thread,
Labels, retries, and starts all gently spread,
Servers wake, clients call, turns rewind,
Types and utils keep every path aligned,
Hooray — providers dance, all neatly led. 🎉

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 43.10% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main objective: bringing OpenCode adapter to feature parity with other adapters, which is the primary focus of this large changeset.
Description check ✅ Passed The PR description provides a comprehensive summary with clear sections for changes, test plan, and objectives. It covers all major modifications including lifecycle events, event handling, rollbackThread implementation, refactoring, test fixes, and error display improvements.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/opencode-adapter-parity
📝 Coding Plan
  • Generate coding plan for human review comments

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

@github-actions github-actions bot added size:XXL 1,000+ changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. labels Mar 16, 2026
Copy link

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/server/src/opencodeServerManager.ts (1)

1-639: ⚠️ Potential issue | 🟡 Minor

Pipeline failure: Run bun run fmt to fix formatting issues.

The CI pipeline reports formatting issues detected by oxfmt. This applies to multiple files in the opencode/ module.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/opencodeServerManager.ts` around lines 1 - 639, Formatting is
failing CI due to oxfmt style issues across the opencode module; run the
repository formatter and commit the changes. Fix by running the project's
formatter command (e.g., "bun run fmt" as suggested by CI) or invoke oxfmt
directly for the opencode files, then review and commit the reformatted files
(ensure changes touch code in OpenCodeServerManager and related files under
opencode/), and push the commit so CI can pass.
🧹 Nitpick comments (6)
apps/server/src/opencode/utils.ts (3)

312-314: readJsonData is a pass-through function — clarify intent or remove.

This function simply returns the promise without transformation. If this is a placeholder for future SDK response normalization, consider adding a comment. Otherwise, it adds indirection without value.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/opencode/utils.ts` around lines 312 - 314, The function
readJsonData currently just returns its input Promise and adds no behavior;
either remove readJsonData and update all callers to use the original promise
directly, or keep it but document its intent and purpose (e.g., add a JSDoc
comment stating it is a deliberate placeholder for future SDK response
normalization/validation) and, if intended, implement minimal normalization
(e.g., await the promise and validate/type-cast the result). Locate the function
by name readJsonData and apply one of these two fixes consistently across the
codebase.

200-210: Consider adding exhaustiveness check for toPermissionReply.

The switch statement covers all current ProviderApprovalDecision values, but adding a default case with an exhaustiveness assertion would catch future additions at compile time.

♻️ Proposed refactor
 export function toPermissionReply(decision: ProviderApprovalDecision): "once" | "always" | "reject" {
   switch (decision) {
     case "acceptForSession":
       return "always";
     case "accept":
       return "once";
     case "decline":
     case "cancel":
       return "reject";
+    default: {
+      const _exhaustive: never = decision;
+      return "reject";
+    }
   }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/opencode/utils.ts` around lines 200 - 210, The
toPermissionReply function's switch over ProviderApprovalDecision should be made
exhaustive: add a default branch that takes a parameter typed as never (or call
an existing assertNever helper) and throws a clear error so the compiler will
flag any new ProviderApprovalDecision variants; update the switch in
toPermissionReply to include that default/exhaustiveness assertion (referencing
the function name toPermissionReply and the type ProviderApprovalDecision) to
ensure future additions are caught at compile time.

356-367: Non-standard unified diff header format.

The stats line @@ +${d.additions},-${d.deletions} @@ doesn't follow standard unified diff format (@@ -start,count +start,count @@). The docstring acknowledges this is an approximation, but consumers expecting parseable unified diff may fail.

If this is intentional for display purposes only, consider renaming to fileDiffsToSummary or adding a clearer docstring indicating it's not parseable as standard unified diff.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/opencode/utils.ts` around lines 356 - 367, The function
fileDiffsToUnifiedDiff emits a non-standard "stats" line (`@@
+${d.additions},-${d.deletions} @@`) which breaks unified-diff consumers; rename
the function to fileDiffsToSummary (or otherwise change its name) and update its
implementation and docstring to clearly indicate it produces a human-readable
summary rather than a parseable unified diff, then update all call sites that
reference fileDiffsToUnifiedDiff to the new name; alternatively, if you need a
real unified diff header, replace the stats line with a proper unified-diff
header format such as `@@ -0,${d.deletions} +0,${d.additions} @@` and ensure
callers expect a parseable diff (adjust tests/docs accordingly).
apps/server/src/opencodeServerManager.ts (1)

471-471: Mutating messageIds.length is unconventional but valid.

While context.messageIds.length = targetIndex + 1 works to truncate the array, using splice is more conventional and explicit:

♻️ Alternative using splice
-    context.messageIds.length = targetIndex + 1;
+    context.messageIds.splice(targetIndex + 1);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/opencodeServerManager.ts` at line 471, The code truncates
context.messageIds by assigning to its length (context.messageIds.length =
targetIndex + 1); replace that with an explicit splice call to make intent clear
and avoid surprising length mutation—e.g., call
context.messageIds.splice(targetIndex + 1) to remove all elements after
targetIndex. Update the occurrence in the function/method where
context.messageIds and targetIndex are used in opencodeServerManager (search for
context.messageIds.length = targetIndex + 1) so other logic relying on
messageIds remains unchanged.
apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts (1)

276-292: detail duplicates message — consider whether additional context is needed.

Both message and detail in the payload are set to truncateDetail(message), which seems redundant. If detail is intended to provide additional context beyond the summary message, consider including more information (e.g., error class, retryability hints). Otherwise, the duplication is harmless but unnecessary.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts` around
lines 276 - 292, The payload currently sets both message and detail to
truncateDetail(message) which duplicates info; update the payload in
ProviderRuntimeIngestion (see runtimePayloadRecord, runtimeErrorClassLabel,
truncateDetail) so message remains truncateDetail(message) but detail contains
additional context (for example the error class label and/or extra fields from
runtimePayloadRecord(event) such as the raw error info or retryability hints),
passed through truncateDetail as well; ensure turnId and maybeSequence usage
remains unchanged.
apps/server/src/opencode/serverLifecycle.ts (1)

19-26: probeServer swallows all errors — consider logging for debugging.

The .catch(() => undefined) silently swallows network errors, which is appropriate for probing, but may complicate debugging connectivity issues. Consider adding debug-level logging.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/opencode/serverLifecycle.ts` around lines 19 - 26, The
probeServer function currently swallows all errors via .catch(() => undefined);
update the catch to log the caught error at debug level (using an existing
logger if available or console.debug) while preserving the undefined return so
behavior is unchanged; reference probeServer, baseUrl, authHeader and
SERVER_PROBE_TIMEOUT_MS when adding the log so the message includes the URL and
any auth context to aid debugging.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/server/src/opencode/eventHandlers.ts`:
- Line 814: The itemId generation using
RuntimeItemId.makeUnsafe(`cmd:${command}:${Date.now()}`) can collide; replace
the Date.now() suffix with a UUID or the project's eventId helper to guarantee
uniqueness (e.g., use randomUUID() from node:crypto or the existing eventId
helper), update the import to include randomUUID if chosen, and construct the
string as `cmd:${command}:${<uuid or eventId>}` so RuntimeItemId.makeUnsafe
produces a collision-resistant id.

In `@apps/server/src/opencode/serverLifecycle.ts`:
- Around line 56-63: The catch block in ensureServer is awaiting serverPromise
again which will re-throw the rejection; change the error handling so you do not
re-await the rejected promise. In ensureServer (which calls spawnOrConnect and
stores serverPromise), either re-throw the caught error (remove the second
await) or return a failure result (e.g., { state: undefined, serverPromise:
undefined }) after clearing serverPromise; pick one behavior and implement it
consistently so the function does not call await serverPromise inside the catch.

---

Outside diff comments:
In `@apps/server/src/opencodeServerManager.ts`:
- Around line 1-639: Formatting is failing CI due to oxfmt style issues across
the opencode module; run the repository formatter and commit the changes. Fix by
running the project's formatter command (e.g., "bun run fmt" as suggested by CI)
or invoke oxfmt directly for the opencode files, then review and commit the
reformatted files (ensure changes touch code in OpenCodeServerManager and
related files under opencode/), and push the commit so CI can pass.

---

Nitpick comments:
In `@apps/server/src/opencode/serverLifecycle.ts`:
- Around line 19-26: The probeServer function currently swallows all errors via
.catch(() => undefined); update the catch to log the caught error at debug level
(using an existing logger if available or console.debug) while preserving the
undefined return so behavior is unchanged; reference probeServer, baseUrl,
authHeader and SERVER_PROBE_TIMEOUT_MS when adding the log so the message
includes the URL and any auth context to aid debugging.

In `@apps/server/src/opencode/utils.ts`:
- Around line 312-314: The function readJsonData currently just returns its
input Promise and adds no behavior; either remove readJsonData and update all
callers to use the original promise directly, or keep it but document its intent
and purpose (e.g., add a JSDoc comment stating it is a deliberate placeholder
for future SDK response normalization/validation) and, if intended, implement
minimal normalization (e.g., await the promise and validate/type-cast the
result). Locate the function by name readJsonData and apply one of these two
fixes consistently across the codebase.
- Around line 200-210: The toPermissionReply function's switch over
ProviderApprovalDecision should be made exhaustive: add a default branch that
takes a parameter typed as never (or call an existing assertNever helper) and
throws a clear error so the compiler will flag any new ProviderApprovalDecision
variants; update the switch in toPermissionReply to include that
default/exhaustiveness assertion (referencing the function name
toPermissionReply and the type ProviderApprovalDecision) to ensure future
additions are caught at compile time.
- Around line 356-367: The function fileDiffsToUnifiedDiff emits a non-standard
"stats" line (`@@ +${d.additions},-${d.deletions} @@`) which breaks unified-diff
consumers; rename the function to fileDiffsToSummary (or otherwise change its
name) and update its implementation and docstring to clearly indicate it
produces a human-readable summary rather than a parseable unified diff, then
update all call sites that reference fileDiffsToUnifiedDiff to the new name;
alternatively, if you need a real unified diff header, replace the stats line
with a proper unified-diff header format such as `@@ -0,${d.deletions}
+0,${d.additions} @@` and ensure callers expect a parseable diff (adjust
tests/docs accordingly).

In `@apps/server/src/opencodeServerManager.ts`:
- Line 471: The code truncates context.messageIds by assigning to its length
(context.messageIds.length = targetIndex + 1); replace that with an explicit
splice call to make intent clear and avoid surprising length mutation—e.g., call
context.messageIds.splice(targetIndex + 1) to remove all elements after
targetIndex. Update the occurrence in the function/method where
context.messageIds and targetIndex are used in opencodeServerManager (search for
context.messageIds.length = targetIndex + 1) so other logic relying on
messageIds remains unchanged.

In `@apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts`:
- Around line 276-292: The payload currently sets both message and detail to
truncateDetail(message) which duplicates info; update the payload in
ProviderRuntimeIngestion (see runtimePayloadRecord, runtimeErrorClassLabel,
truncateDetail) so message remains truncateDetail(message) but detail contains
additional context (for example the error class label and/or extra fields from
runtimePayloadRecord(event) such as the raw error info or retryability hints),
passed through truncateDetail as well; ensure turnId and maybeSequence usage
remains unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 22ddc973-de7d-4a1d-ba87-f6f434699e52

📥 Commits

Reviewing files that changed from the base of the PR and between 5239018 and 92713a3.

📒 Files selected for processing (9)
  • apps/server/src/opencode/errors.ts
  • apps/server/src/opencode/eventHandlers.ts
  • apps/server/src/opencode/index.ts
  • apps/server/src/opencode/serverLifecycle.ts
  • apps/server/src/opencode/types.ts
  • apps/server/src/opencode/utils.ts
  • apps/server/src/opencodeServerManager.ts
  • apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts
  • apps/server/src/provider/Layers/OpenCodeAdapter.test.ts

…eServer catch

- Replace Date.now() with randomUUID() in command.executed itemId to prevent
  millisecond-level collisions
- Fix ensureServer catch block that re-awaited the rejected promise instead
  of propagating the error for retry
Copy link

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

♻️ Duplicate comments (2)
apps/server/src/opencode/eventHandlers.ts (1)

810-810: ⚠️ Potential issue | 🟡 Minor

Use collision-resistant IDs for command execution items.

Date.now() can generate duplicate itemIds under concurrent/rapid command events.

🐛 Suggested fix
+import { randomUUID } from "node:crypto";
 import { ApprovalRequestId, RuntimeItemId, RuntimeRequestId } from "@t3tools/contracts";
@@
-  const itemId = RuntimeItemId.makeUnsafe(`cmd:${command}:${Date.now()}`);
+  const itemId = RuntimeItemId.makeUnsafe(`cmd:${command}:${randomUUID()}`);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/opencode/eventHandlers.ts` at line 810, The itemId generation
using RuntimeItemId.makeUnsafe with Date.now() is vulnerable to collisions under
concurrent events; update the construction of item IDs in the handler where
"itemId" is created (RuntimeItemId.makeUnsafe(`cmd:${command}:${Date.now()}`))
to use a collision-resistant identifier such as crypto.randomUUID() or a
nanoid-style random suffix instead of Date.now(), e.g., combine the command with
a UUID or secure random string to ensure uniqueness across rapid/concurrent
command executions.
apps/server/src/opencode/serverLifecycle.ts (1)

63-65: ⚠️ Potential issue | 🟠 Major

Don’t re-await the rejected serverPromise in ensureServer.

The catch path re-awaits the same rejected promise, so it just throws again and never returns the fallback object.

🐛 Suggested fix
   const serverPromise = spawnOrConnect(options);
-  try {
-    const state = await serverPromise;
-    return { state, serverPromise };
-  } catch {
-    return { state: await serverPromise, serverPromise: undefined };
-  }
+  const state = await serverPromise;
+  return { state, serverPromise };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/opencode/serverLifecycle.ts` around lines 63 - 65, The catch
block in ensureServer currently re-awaits the rejected serverPromise which will
throw again; change the catch to not await the rejected promise and instead
return a safe fallback (e.g., { state: undefined, serverPromise: undefined }) or
use serverPromise.catch(() => undefined) to convert rejection to a resolved
value before awaiting; update the catch to return { state: undefined,
serverPromise: undefined } (or { state: await serverPromise.catch(() =>
undefined), serverPromise: undefined }) and ensure references to ensureServer
and serverPromise are used to locate and modify the code.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/server/src/opencode/utils.ts`:
- Around line 167-171: Replace the manual copy-and-sort pattern with
Array.prototype.toSorted to satisfy the unicorn lint rule; specifically, change
the sorting in utils.ts that uses [...providers].sort((a,b)=>{...}) to
providers.toSorted((a,b)=>{...}) while keeping the same comparator logic that
uses a.name || a.id and localeCompare, and ensure the variable `providers` is an
array in the same scope so `toSorted` can be called directly.

In `@apps/server/src/opencodeServerManager.ts`:
- Around line 456-466: In rollbackThread, validate the numTurns parameter before
computing targetIndex: ensure numTurns is a positive integer (use
Number.isInteger and numTurns > 0) and that it does not exceed ids.length; if
invalid, throw a clear Error (e.g., "Invalid numTurns: must be a positive
integer and <= tracked turns"). Then compute targetIndex using the
validated/clamped numTurns (e.g., Math.min(numTurns, ids.length)) so
targetMessageId from context.messageIds cannot be out of bounds before calling
session.revert/readJsonData; reference the rollbackThread method,
context.messageIds, and session.revert when making the checks and error.

---

Duplicate comments:
In `@apps/server/src/opencode/eventHandlers.ts`:
- Line 810: The itemId generation using RuntimeItemId.makeUnsafe with Date.now()
is vulnerable to collisions under concurrent events; update the construction of
item IDs in the handler where "itemId" is created
(RuntimeItemId.makeUnsafe(`cmd:${command}:${Date.now()}`)) to use a
collision-resistant identifier such as crypto.randomUUID() or a nanoid-style
random suffix instead of Date.now(), e.g., combine the command with a UUID or
secure random string to ensure uniqueness across rapid/concurrent command
executions.

In `@apps/server/src/opencode/serverLifecycle.ts`:
- Around line 63-65: The catch block in ensureServer currently re-awaits the
rejected serverPromise which will throw again; change the catch to not await the
rejected promise and instead return a safe fallback (e.g., { state: undefined,
serverPromise: undefined }) or use serverPromise.catch(() => undefined) to
convert rejection to a resolved value before awaiting; update the catch to
return { state: undefined, serverPromise: undefined } (or { state: await
serverPromise.catch(() => undefined), serverPromise: undefined }) and ensure
references to ensureServer and serverPromise are used to locate and modify the
code.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7c130015-418d-4c7e-924b-84193aa0eea6

📥 Commits

Reviewing files that changed from the base of the PR and between 92713a3 and 7fd975a.

📒 Files selected for processing (4)
  • apps/server/src/opencode/eventHandlers.ts
  • apps/server/src/opencode/serverLifecycle.ts
  • apps/server/src/opencode/utils.ts
  • apps/server/src/opencodeServerManager.ts

Copy link

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

🧹 Nitpick comments (1)
apps/server/src/opencode/serverLifecycle.ts (1)

73-78: Consider probing user-provided serverUrl for early failure detection.

When serverUrl is explicitly provided, the function returns immediately without verifying the server is reachable. If the server is down or unreachable, downstream operations will fail with less context. A probe here would provide a clearer early error.

This is a minor UX improvement and may have been intentionally omitted to trust user configuration.

♻️ Optional: Add health probe for user-provided serverUrl
   if (options?.serverUrl) {
+    const healthy = await probeServer(options.serverUrl, authHeader);
+    if (!healthy) {
+      throw new Error(`OpenCode server at ${options.serverUrl} is not reachable`);
+    }
     return {
       baseUrl: options.serverUrl,
       ...(authHeader ? { authHeader } : {}),
     };
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/opencode/serverLifecycle.ts` around lines 73 - 78, The early
return when options?.serverUrl is set skips any reachability check; update the
logic around the options.serverUrl return to perform a lightweight health probe
(e.g., GET or HEAD to a /health or root path with a short timeout) using the
same authHeader if present, and only return the { baseUrl, ...(authHeader ? {
authHeader } : {}) } after the probe succeeds; on failure throw or propagate a
clear error that includes the serverUrl and probe error so callers get
immediate, actionable feedback (refer to options.serverUrl, authHeader and the
code block that builds the returned object).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/server/src/opencode/eventHandlers.ts`:
- Around line 472-480: The answers object mixes string and array values; change
the missing-answer branch in the mapping that builds answers (the map over
pending?.questions using question.answerIndex and answerArrays) to return an
empty array instead of an empty string so every question id maps to an array;
ensure the mapped value for present answers remains the filtered array
(answer.filter(...)) so the resulting answers object has a uniform Array type
for all question ids.
- Around line 752-801: Both handleVcsBranchUpdatedEvent and
handleFileEditedEvent are missing sessionID validation and may emit events into
the wrong session; at the top of each function (before emitter.emitRuntimeEvent)
extract the sessionID from event.properties (const { sessionID: sessionId } =
event.properties) and return early if sessionId is present and differs from
context.providerSessionId (if (sessionId && sessionId !==
context.providerSessionId) { return; }), matching the pattern used in
handleSessionErrorEvent to ensure only matching provider sessions are processed.

---

Nitpick comments:
In `@apps/server/src/opencode/serverLifecycle.ts`:
- Around line 73-78: The early return when options?.serverUrl is set skips any
reachability check; update the logic around the options.serverUrl return to
perform a lightweight health probe (e.g., GET or HEAD to a /health or root path
with a short timeout) using the same authHeader if present, and only return the
{ baseUrl, ...(authHeader ? { authHeader } : {}) } after the probe succeeds; on
failure throw or propagate a clear error that includes the serverUrl and probe
error so callers get immediate, actionable feedback (refer to options.serverUrl,
authHeader and the code block that builds the returned object).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 41c6eac7-4118-4307-85df-fe7256c8f679

📥 Commits

Reviewing files that changed from the base of the PR and between 7fd975a and 540a3ef.

📒 Files selected for processing (2)
  • apps/server/src/opencode/eventHandlers.ts
  • apps/server/src/opencode/serverLifecycle.ts

Comment on lines +472 to +480
const answers = Object.fromEntries(
(pending?.questions ?? []).map((question) => {
const answer = answerArrays[question.answerIndex];
if (!answer) {
return [question.id, ""];
}
return [question.id, answer.filter((value) => value.length > 0)];
}),
);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Type inconsistency: mixed string/array values in answers object.

Line 476 returns an empty string "" for missing answers, while line 478 returns a filtered array for present answers. This results in an answers object with inconsistent value types, which could cause issues for consumers expecting uniform array values.

🐛 Proposed fix for type consistency
   const answers = Object.fromEntries(
     (pending?.questions ?? []).map((question) => {
       const answer = answerArrays[question.answerIndex];
       if (!answer) {
-        return [question.id, ""];
+        return [question.id, []];
       }
       return [question.id, answer.filter((value) => value.length > 0)];
     }),
   );
📝 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
const answers = Object.fromEntries(
(pending?.questions ?? []).map((question) => {
const answer = answerArrays[question.answerIndex];
if (!answer) {
return [question.id, ""];
}
return [question.id, answer.filter((value) => value.length > 0)];
}),
);
const answers = Object.fromEntries(
(pending?.questions ?? []).map((question) => {
const answer = answerArrays[question.answerIndex];
if (!answer) {
return [question.id, []];
}
return [question.id, answer.filter((value) => value.length > 0)];
}),
);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/opencode/eventHandlers.ts` around lines 472 - 480, The
answers object mixes string and array values; change the missing-answer branch
in the mapping that builds answers (the map over pending?.questions using
question.answerIndex and answerArrays) to return an empty array instead of an
empty string so every question id maps to an array; ensure the mapped value for
present answers remains the filtered array (answer.filter(...)) so the resulting
answers object has a uniform Array type for all question ids.

Kilo is a fork of OpenCode sharing the same SDK and SSE API. Apply the
same modular extraction and feature parity improvements:

- Extract monolithic kiloServerManager.ts into kilo/ module directory
  (types, errors, utils, eventHandlers, serverLifecycle)
- Emit session.configured and session.exited lifecycle events
- Forward session.diff as turn.diff.updated with unified diff conversion
- Handle new SSE events: session.compacted, session.updated,
  vcs.branch.updated, file.edited, command.executed, message.part.removed
- Enrich events: todo priority, isRetryable, permission title/metadata
- Implement rollbackThread via session.revert API with message ID tracking
- Fix flaky KiloAdapter stream forwarding test (same race condition fix)
Copy link

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

🧹 Nitpick comments (6)
apps/server/src/kilo/utils.ts (1)

319-321: readJsonData is currently a pass-through — is this intentional?

The function simply returns the promise without transformation. If this is a placeholder for future error handling or response unwrapping, consider adding a TODO comment. Otherwise, callers could use the promise directly.

📝 If intentional, add clarifying comment
+/**
+ * Unwraps SDK responses. Currently a pass-through but provides a
+ * consistent hook point for future error handling or response normalization.
+ */
 export async function readJsonData<T>(promise: Promise<T>): Promise<T> {
   return promise;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/kilo/utils.ts` around lines 319 - 321, The readJsonData
function is currently a no-op that just returns the incoming Promise; update it
to either be removed (and callers use the Promise directly) or make its intent
explicit: replace the pass-through with an awaited try/catch that unwraps the
value and adds contextual error handling/validation (or at minimum add a TODO
comment explaining it’s a deliberate placeholder). Locate the function
readJsonData in the file and either delete usages and the function or implement
await + try/catch to enrich errors or validate the JSON before returning, or add
a clarifying comment/TODO indicating why it intentionally returns the promise.
apps/server/src/kilo/types.ts (2)

104-109: Consider making KiloDiscoveredModel properties readonly for consistency.

Other types in this file use readonly properties (e.g., KiloModel, QuestionInfo), but KiloDiscoveredModel has mutable properties. This inconsistency could lead to accidental mutations.

📝 Suggested fix
 export type KiloDiscoveredModel = {
-  slug: string;
-  name: string;
-  variants?: ReadonlyArray<string>;
-  connected?: boolean;
+  readonly slug: string;
+  readonly name: string;
+  readonly variants?: ReadonlyArray<string>;
+  readonly connected?: boolean;
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/kilo/types.ts` around lines 104 - 109, The
KiloDiscoveredModel type currently exposes mutable properties (slug, name,
variants, connected) which is inconsistent with other readonly types; update the
KiloDiscoveredModel declaration so each property is readonly (e.g., readonly
slug, readonly name, readonly variants?: ReadonlyArray<string>, readonly
connected?: boolean) to prevent accidental mutations and align with KiloModel
and QuestionInfo.

538-540: Minor type redundancy: directory is defined in both OpencodeClientConfig and OpencodeClientOptions.

OpencodeClientConfig already has directory?: string at line 448. The intersection type works but creates conceptual duplication.

📝 Suggested simplification
-export type OpencodeClientOptions = OpencodeClientConfig & {
-  directory?: string;
-};
+export type OpencodeClientOptions = OpencodeClientConfig;

Or if the intent is to make directory required in options but optional in config, the type should reflect that explicitly.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/kilo/types.ts` around lines 538 - 540, OpencodeClientOptions
currently repeats the optional property directory which already exists on
OpencodeClientConfig; remove the redundancy by deleting directory?: string from
the OpencodeClientOptions declaration (or if the intention is to make directory
required for options, change the type there to directory: string while keeping
OpencodeClientConfig.directory?: string) so that only OpencodeClientOptions or
OpencodeClientConfig conveys the intended optional/required semantics for
directory (refer to OpencodeClientOptions, OpencodeClientConfig, and the
directory property).
apps/server/src/kilo/eventHandlers.ts (2)

803-854: Command execution emits start+complete synchronously — verify this matches expected semantics.

handleCommandExecutedEvent emits both item.started and item.completed events in immediate succession with the same timestamp. This makes sense if command.executed is a past-tense notification (the command already ran), but could be confusing if consumers expect a duration between start and completion.

If this is the intended behavior, consider adding a brief comment explaining why both events are emitted together.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/kilo/eventHandlers.ts` around lines 803 - 854,
handleCommandExecutedEvent currently emits both "item.started" and
"item.completed" back-to-back with identical timestamps; confirm this is
intentional and add a concise comment above handleCommandExecutedEvent
explaining that EventCommandExecuted is a past-tense notification (the command
already ran) so we emit both item.started and item.completed synchronously to
represent an instantaneous dynamic_tool_call (include mention of the emitted
event types "item.started" and "item.completed" and the use of
RuntimeItemId.makeUnsafe/randomUUID for the itemId so readers understand why
timestamps match).

53-114: Consider adding a default case for exhaustiveness.

The switch statement handles all known event types but lacks a default case. If the SDK introduces new event types, they'll be silently ignored. While message.part.removed is explicitly silenced with a comment, unknown events would pass through without any indication.

📝 Optional: Add exhaustiveness check
     case "command.executed":
       handleCommandExecutedEvent(emitter, context, event);
       return;
+    default:
+      // Exhaustiveness check - TypeScript will error if a new event type
+      // is added to KiloEvent but not handled here.
+      event satisfies never;
   }

Alternatively, if silent passthrough is intentional, add a comment documenting that behavior.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/kilo/eventHandlers.ts` around lines 53 - 114, The switch in
handleEvent lacks a default branch so unknown event types are silently ignored;
add a default case that either logs a warning (including event.type and the
event payload, e.g., via processLogger.warn or emitter.emit("warning", ...)) or
enforces exhaustiveness with a TypeScript never-check (assign event.type to a
never-typed variable) so new event types are surfaced during
compilation/runtime; reference handleEvent and event.type when adding the
default handling.
apps/server/src/kilo/errors.ts (1)

3-5: Minor grammatical issue in docstrings.

The article "an" should be "a" before "Kilo" (since "Kilo" starts with a consonant sound). This appears in multiple comments throughout the file (lines 4, 51, 68).

📝 Suggested fix
 /**
- * Maps an Kilo error name to a runtime error class used by the
+ * Maps a Kilo error name to a runtime error class used by the
  * orchestration layer to categorize errors for display.
  */

Similar fixes needed at lines 51 and 68.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/kilo/errors.ts` around lines 3 - 5, Fix the minor grammatical
errors in the file-level and inline docstrings that use "an Kilo" — change the
article "an" to "a" wherever Kilo is referenced (for example in the top comment
describing the mapping and the docstrings around the KiloErrorToRuntimeErrorMap
/ mapKiloErrorNameToRuntimeError symbols and any comments near functions or
constants around lines 51 and 68); update those comment strings only, preserving
punctuation and formatting otherwise.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/server/src/kilo/serverLifecycle.ts`:
- Around line 60-68: The catch block in ensureServer (where serverPromise is
assigned from spawnOrConnect) contains a misleading comment about clearing the
promise but does not actually clear it; update the code to either remove that
comment or make the function responsible for clearing the cached promise;
specifically, modify the catch in ensureServer (the block that currently
rethrows the caught error after "Clear the promise..." comment) to remove the
misleading comment and, if you prefer making this function responsible,
explicitly clear the cached.serverPromise reference before rethrowing; also
update the function-level docstring to state that KiloServerManager.ensureServer
currently handles clearing in its finally block if you choose to keep clearing
out of ensureServer.

In `@apps/server/src/kiloServerManager.ts`:
- Around line 451-471: The rollbackThread method currently computes targetIndex
even when numTurns >= ids.length which causes a no-op revert to the first
message; add validation at the start of rollbackThread to check that numTurns is
a positive integer less than ids.length and throw a clear Error (e.g., `throw
new Error(\`Cannot rollback ${numTurns} turns: only ${ids.length} turns
tracked\`)`) when the request exceeds available messages; reference and validate
the local variables `numTurns`, `ids`/`context.messageIds` and keep the existing
revert logic (targetIndex, targetMessageId, client.session.revert) unchanged
after the guard.

---

Nitpick comments:
In `@apps/server/src/kilo/errors.ts`:
- Around line 3-5: Fix the minor grammatical errors in the file-level and inline
docstrings that use "an Kilo" — change the article "an" to "a" wherever Kilo is
referenced (for example in the top comment describing the mapping and the
docstrings around the KiloErrorToRuntimeErrorMap /
mapKiloErrorNameToRuntimeError symbols and any comments near functions or
constants around lines 51 and 68); update those comment strings only, preserving
punctuation and formatting otherwise.

In `@apps/server/src/kilo/eventHandlers.ts`:
- Around line 803-854: handleCommandExecutedEvent currently emits both
"item.started" and "item.completed" back-to-back with identical timestamps;
confirm this is intentional and add a concise comment above
handleCommandExecutedEvent explaining that EventCommandExecuted is a past-tense
notification (the command already ran) so we emit both item.started and
item.completed synchronously to represent an instantaneous dynamic_tool_call
(include mention of the emitted event types "item.started" and "item.completed"
and the use of RuntimeItemId.makeUnsafe/randomUUID for the itemId so readers
understand why timestamps match).
- Around line 53-114: The switch in handleEvent lacks a default branch so
unknown event types are silently ignored; add a default case that either logs a
warning (including event.type and the event payload, e.g., via
processLogger.warn or emitter.emit("warning", ...)) or enforces exhaustiveness
with a TypeScript never-check (assign event.type to a never-typed variable) so
new event types are surfaced during compilation/runtime; reference handleEvent
and event.type when adding the default handling.

In `@apps/server/src/kilo/types.ts`:
- Around line 104-109: The KiloDiscoveredModel type currently exposes mutable
properties (slug, name, variants, connected) which is inconsistent with other
readonly types; update the KiloDiscoveredModel declaration so each property is
readonly (e.g., readonly slug, readonly name, readonly variants?:
ReadonlyArray<string>, readonly connected?: boolean) to prevent accidental
mutations and align with KiloModel and QuestionInfo.
- Around line 538-540: OpencodeClientOptions currently repeats the optional
property directory which already exists on OpencodeClientConfig; remove the
redundancy by deleting directory?: string from the OpencodeClientOptions
declaration (or if the intention is to make directory required for options,
change the type there to directory: string while keeping
OpencodeClientConfig.directory?: string) so that only OpencodeClientOptions or
OpencodeClientConfig conveys the intended optional/required semantics for
directory (refer to OpencodeClientOptions, OpencodeClientConfig, and the
directory property).

In `@apps/server/src/kilo/utils.ts`:
- Around line 319-321: The readJsonData function is currently a no-op that just
returns the incoming Promise; update it to either be removed (and callers use
the Promise directly) or make its intent explicit: replace the pass-through with
an awaited try/catch that unwraps the value and adds contextual error
handling/validation (or at minimum add a TODO comment explaining it’s a
deliberate placeholder). Locate the function readJsonData in the file and either
delete usages and the function or implement await + try/catch to enrich errors
or validate the JSON before returning, or add a clarifying comment/TODO
indicating why it intentionally returns the promise.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 56abafd3-8337-4a42-abe2-2ae7c1a10d3d

📥 Commits

Reviewing files that changed from the base of the PR and between 540a3ef and 252d23d.

📒 Files selected for processing (8)
  • apps/server/src/kilo/errors.ts
  • apps/server/src/kilo/eventHandlers.ts
  • apps/server/src/kilo/index.ts
  • apps/server/src/kilo/serverLifecycle.ts
  • apps/server/src/kilo/types.ts
  • apps/server/src/kilo/utils.ts
  • apps/server/src/kiloServerManager.ts
  • apps/server/src/provider/Layers/KiloAdapter.test.ts

- Use toSorted() instead of sort() on copied arrays (unicorn lint rule)
- Validate numTurns in rollbackThread (positive integer, within bounds)
- Add sessionID filtering to vcs.branch.updated and file.edited handlers
- Fix misleading ensureServer catch comment (caller handles cleanup)
Copy link

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

♻️ Duplicate comments (1)
apps/server/src/opencode/eventHandlers.ts (1)

472-480: ⚠️ Potential issue | 🟡 Minor

Type inconsistency: mixed string/array values in answers object.

Line 476 returns an empty string "" for missing answers, while line 478 returns a filtered array. This creates an answers object with inconsistent value types (string | string[]), which could cause issues for consumers expecting uniform array values.

🐛 Proposed fix for type consistency
   const answers = Object.fromEntries(
     (pending?.questions ?? []).map((question) => {
       const answer = answerArrays[question.answerIndex];
       if (!answer) {
-        return [question.id, ""];
+        return [question.id, []];
       }
       return [question.id, answer.filter((value) => value.length > 0)];
     }),
   );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/opencode/eventHandlers.ts` around lines 472 - 480, The
answers object mixes strings and arrays causing type inconsistency: when an
answer is missing the code returns "" but otherwise returns an array; update the
mapping in the answers construction to return an empty array ([]) for missing
answers so every value is a string[]; adjust the branch that uses
answerArrays[question.answerIndex] (and any consumers expecting string[]) if
necessary to reflect the uniform string[] shape for question.id entries.
🧹 Nitpick comments (3)
apps/server/src/opencode/utils.ts (2)

310-313: Consider adding documentation for the expected URL format.

The regex pattern extracts the server URL from startup output. Adding a brief comment about what the expected output looks like would aid future maintenance.

📝 Suggested documentation
+/**
+ * Parses the server URL from OpenCode startup output.
+ * Expects a line like: "opencode server listening on http://127.0.0.1:6733"
+ */
 export function parseServerUrl(output: string): string | undefined {
   const match = output.match(/opencode server listening on\s+(https?:\/\/[^\s]+)(?=\r?\n)/);
   return match?.[1];
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/opencode/utils.ts` around lines 310 - 313, Add a brief
comment above parseServerUrl describing the expected startup output format and
what the regex captures: show an example line like "opencode server listening on
https://host:port" (http or https), note that the function returns the first
captured URL (protocol + host + optional port/path) and that the regex looks for
the URL followed by a newline; mention any assumptions (e.g., no surrounding
punctuation) so future maintainers understand why the pattern uses /opencode
server listening on\s+(https?:\/\/[^\s]+)(?=\r?\n)/.

319-321: The readJsonData function appears to be a no-op.

This function simply returns the input promise without transformation. If this is a placeholder for future SDK response unwrapping, consider adding a comment explaining its purpose.

📝 Suggested documentation
+/**
+ * Unwraps SDK response data. Currently a pass-through; exists to provide
+ * a consistent interface if the SDK response format changes.
+ */
 export async function readJsonData<T>(promise: Promise<T>): Promise<T> {
   return promise;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/opencode/utils.ts` around lines 319 - 321, The readJsonData
function is currently a no-op (it just returns the input promise); either
document it as an intentional placeholder or implement proper unwrapping: if
it’s meant as a future SDK response adapter, add a JSDoc above readJsonData
explaining its purpose and intended behavior; otherwise change readJsonData to
await the incoming promise and return the resolved payload (for common SDK
responses, extract and return the .data field when present) and update its
signature/typing accordingly so callers receive the actual JSON value rather
than the raw promise.
apps/server/src/opencode/serverLifecycle.ts (1)

119-135: Consider: onExit async callback may delay rejection.

The onExit handler calls probeServer asynchronously after cleanup. If the probe takes time (up to SERVER_PROBE_TIMEOUT_MS), the Promise resolution is delayed. This is likely intentional to support server reuse scenarios, but worth noting that the startup flow may hang briefly on abrupt exits.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/opencode/serverLifecycle.ts` around lines 119 - 135, The
onExit handler currently awaits probeServer which can delay rejection; change
onExit so that after cleanup it immediately rejects if the process exited with a
non-zero code (include the trimmed output in the Error) and only run probeServer
for reuse when the exit code is zero; additionally ensure probeServer is invoked
with a bounded timeout (use SERVER_PROBE_TIMEOUT_MS or wrap probeServer in a
Promise.race) so the startup promise cannot hang indefinitely; refer to onExit,
probeServer, cleanup, resolve, reject, and SERVER_PROBE_TIMEOUT_MS when making
the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@apps/server/src/opencode/eventHandlers.ts`:
- Around line 472-480: The answers object mixes strings and arrays causing type
inconsistency: when an answer is missing the code returns "" but otherwise
returns an array; update the mapping in the answers construction to return an
empty array ([]) for missing answers so every value is a string[]; adjust the
branch that uses answerArrays[question.answerIndex] (and any consumers expecting
string[]) if necessary to reflect the uniform string[] shape for question.id
entries.

---

Nitpick comments:
In `@apps/server/src/opencode/serverLifecycle.ts`:
- Around line 119-135: The onExit handler currently awaits probeServer which can
delay rejection; change onExit so that after cleanup it immediately rejects if
the process exited with a non-zero code (include the trimmed output in the
Error) and only run probeServer for reuse when the exit code is zero;
additionally ensure probeServer is invoked with a bounded timeout (use
SERVER_PROBE_TIMEOUT_MS or wrap probeServer in a Promise.race) so the startup
promise cannot hang indefinitely; refer to onExit, probeServer, cleanup,
resolve, reject, and SERVER_PROBE_TIMEOUT_MS when making the change.

In `@apps/server/src/opencode/utils.ts`:
- Around line 310-313: Add a brief comment above parseServerUrl describing the
expected startup output format and what the regex captures: show an example line
like "opencode server listening on https://host:port" (http or https), note that
the function returns the first captured URL (protocol + host + optional
port/path) and that the regex looks for the URL followed by a newline; mention
any assumptions (e.g., no surrounding punctuation) so future maintainers
understand why the pattern uses /opencode server listening
on\s+(https?:\/\/[^\s]+)(?=\r?\n)/.
- Around line 319-321: The readJsonData function is currently a no-op (it just
returns the input promise); either document it as an intentional placeholder or
implement proper unwrapping: if it’s meant as a future SDK response adapter, add
a JSDoc above readJsonData explaining its purpose and intended behavior;
otherwise change readJsonData to await the incoming promise and return the
resolved payload (for common SDK responses, extract and return the .data field
when present) and update its signature/typing accordingly so callers receive the
actual JSON value rather than the raw promise.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8629b5b0-c7e1-4d2e-a68a-5563db311f26

📥 Commits

Reviewing files that changed from the base of the PR and between 252d23d and 39d48d2.

📒 Files selected for processing (8)
  • apps/server/src/kilo/eventHandlers.ts
  • apps/server/src/kilo/serverLifecycle.ts
  • apps/server/src/kilo/utils.ts
  • apps/server/src/kiloServerManager.ts
  • apps/server/src/opencode/eventHandlers.ts
  • apps/server/src/opencode/serverLifecycle.ts
  • apps/server/src/opencode/utils.ts
  • apps/server/src/opencodeServerManager.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/server/src/kilo/serverLifecycle.ts

@aaditagrawal aaditagrawal merged commit 5ddbe94 into main Mar 16, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL 1,000+ changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant