Skip to content

Develop - #1097

Closed
keenJoe wants to merge 7 commits into
siteboon:mainfrom
keenJoe:develop
Closed

Develop#1097
keenJoe wants to merge 7 commits into
siteboon:mainfrom
keenJoe:develop

Conversation

@keenJoe

@keenJoe keenJoe commented Aug 4, 2026

Copy link
Copy Markdown

add pi agent

Summary by CodeRabbit

  • New Features
    • Added Pi as a selectable AI provider for chat, terminal sessions, model selection, authentication status, skills, session history, and token usage.
    • Added Pi branding, localized labels, provider settings, permission options, and reasoning-effort controls.
    • Added support for resuming Pi sessions and synchronizing conversation history.
  • Bug Fixes
    • Improved structured error-message handling and realtime message deduplication.
    • Improved display and preservation of streaming reasoning, tool results, and message metadata.
  • Documentation
    • Added provider switching, integration, collaboration terminology, and future-work documentation.

keenJoe and others added 7 commits July 31, 2026 18:09
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- shell: append user-level bin dirs (~/.local/bin etc.) to PATH in spawned
  pty so CLIs like claude are reachable when npm strips shell-rc PATH entries
- auth: handle structured error object {code, message} in
  resolveApiErrorMessage to prevent React crash (white screen) on login failure

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add Pi as a full provider alongside the existing four, wired through the
same IProvider contract: live chat runtime (RPC mode, agent_settled as the
sole success terminal), model catalog, install/auth status, session history
and disk sync, skills discovery, and permission modes. Unsupported
capabilities (MCP) surface an explicit ERR rather than empty success.

- Pin @earendil-works/pi-coding-agent@0.83.0 (exact version)
- New list/pi facets: paths, rpc-client, session-store, runtime, models,
  auth, sessions, session-synchronizer, skills, token-usage, mcp
- Central wiring: registry, capabilities, token-usage, watcher, agent routes
- Frontend: pi provider type, brand/logo, per-provider model state,
  permission picker, MCP exclusion, /skill:<name> display
- Fix runtime close-before-settle detection via real process exit hook
- Skip MCP-unsupported providers in global add/remove instead of reporting
  a spurious failure

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Pi is added as a complete provider with RPC runtime, session parsing, models, authentication, skills, capabilities, backend routing, WebSocket support, and frontend integration. Realtime chat reconciliation now handles streaming messages, thinking metadata, tool results, and duplicate snapshots.

Changes

Pi provider integration

Layer / File(s) Summary
Runtime and session foundation
server/modules/providers/list/pi/...
Adds RPC lifecycle management, event normalization, abort handling, JSONL session parsing, history reconstruction, and token usage extraction.
Provider facets and backend wiring
server/modules/providers/..., server/modules/agent/..., server/modules/websocket/...
Registers Pi and exposes models, authentication, skills, MCP capability errors, session synchronization, token usage, agent routing, watcher paths, and shell commands.
Frontend provider support
src/components/..., src/i18n/..., src/types/app.ts
Adds Pi model state, effort values, authentication endpoints, onboarding and settings entries, logos, labels, localization, and capability-specific UI behavior.
Realtime message reconciliation
src/stores/..., src/components/chat/...
Merges realtime messages by ID and sequence, preserves streaming and duration metadata, deduplicates thinking messages, and normalizes nested tool results.
Provider design artifacts
docs/..., openspec/..., CONTEXT.md, TODO.md
Adds provider integration, session switching, provider seam, implementation, and test-definition documentation.
Development configuration
package.json, .gitignore, openspec/config.yaml
Adds the Pi RPC dependency, switches the development script to watch mode, and updates ignored project files.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant AgentRouter
  participant PiRuntime
  participant PiRpcClient
  participant SessionStore
  Client->>AgentRouter: send provider pi request
  AgentRouter->>PiRuntime: run message and session options
  PiRuntime->>PiRpcClient: start RPC and bind native session
  PiRpcClient-->>PiRuntime: stream text, thinking, and tool events
  PiRuntime-->>Client: normalized realtime messages
  PiRpcClient-->>PiRuntime: agent_settled
  PiRuntime->>SessionStore: load persisted history and usage
  SessionStore-->>Client: reconciled session history
Loading

Possibly related PRs

  • siteboon/claudecodeui#422 — Adds another CLI provider across the same provider, agent, session, runtime, and chat integration seams.
  • siteboon/claudecodeui#666 — Introduces shared provider registry, contracts, services, and runtime/session architecture extended by this change.
  • siteboon/claudecodeui#1058 — Adds a provider across registry, runtime, models, sessions, routes, capabilities, and frontend integrations.

Suggested reviewers: viper151

Poem

A rabbit hops through Pi’s new stream,
RPC stars weave a session dream.
Models bloom and messages flow,
Thinking snapshots gently glow.
Tools and history meet in line—
One tidy burrow, state in time.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.83% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title "Develop" is too generic and does not identify the Pi provider integration or other primary changes. Replace the title with a concise summary of the main change, such as "Add Pi coding agent provider integration".
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ 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.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 11

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

🟡 Minor comments (10)
docs/pi-provider-integration-plan.md-3-7 (1)

3-7: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Update the Pi implementation status documents.

The PR implements Pi support, but the plan says “待实施,” the TODO still lists Pi integration, and all implementation tasks remain unchecked. This makes the delivered backend and frontend behavior unclear.

  • docs/pi-provider-integration-plan.md#L3-L7: Mark the plan as implemented, or mark it as a historical design document.
  • TODO.md#L3-L4: Remove the completed Pi integration task or replace it with remaining work.
  • openspec/changes/add-pi-provider/tasks.md#L21-L61: Mark completed tasks, or state that the checklist is historical.

As per path instructions, “document the backend/frontend behavior clearly.”

🤖 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 `@docs/pi-provider-integration-plan.md` around lines 3 - 7, Update the Pi
implementation status documentation: in docs/pi-provider-integration-plan.md
lines 3-7, mark the plan implemented or historical; in TODO.md lines 3-4, remove
the completed Pi integration task or replace it with remaining work; and in
openspec/changes/add-pi-provider/tasks.md lines 21-61, mark completed tasks or
identify the checklist as historical, clearly reflecting the delivered backend
and frontend behavior.

Source: Path instructions

openspec/changes/refactor-provider-seams/test-definition.md-20-20 (1)

20-20: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

使正常路径样本数与描述一致。

该行写明样本数为 6,但描述列出了 5 个能力契约场景和 4 个 provider characterization 冒烟场景,共 9 个命名样本组。请修正数量,或重写描述以明确样本的分组方式。

🤖 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 `@openspec/changes/refactor-provider-seams/test-definition.md` at line 20,
Update the normal-path entry in the test-definition table so the stated sample
count matches the described five capability-contract scenarios plus four
provider characterization smoke scenarios, or revise the description to
explicitly define the grouping represented by the count.
src/components/onboarding/view/subcomponents/AgentConnectionsStep.tsx-49-55 (1)

49-55: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Keep one reachable Pi login action visible.

The onboarding Pi card has hideLogin: true, but it passes onLogin={() => onOpenProviderLogin(providerCard.provider)} to AgentConnectionCard. The login button is hidden regardless of auth status, except when status.authenticated, so an unauthenticated Pi card has no modal entry point. Expose a custom Pi action or remove the onboarding card.

🤖 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/components/onboarding/view/subcomponents/AgentConnectionsStep.tsx` around
lines 49 - 55, Keep an unauthenticated Pi login entry point reachable: update
the Pi configuration in AgentConnectionsStep.tsx (lines 49-55) by removing
hideLogin or adding a visible custom action that invokes onOpenProviderLogin.
Apply the equivalent reachability fix to the Pi entry in AccountContent.tsx
(line 123), if it uses the same hidden-login configuration.
server/modules/providers/list/pi/pi-session-store.provider.ts-133-139 (1)

133-139: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Make snapshot contents immutable.

Object.freeze() freezes only the outer object or array. snapshot.entries[0], snapshot.messages[0].message, and snapshot.lastUsage.cost remain writable. This violates the documented immutable snapshot contract.

Deep-freeze parsed values or expose immutable copies. Add a test that attempts nested mutation.

🤖 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/pi/pi-session-store.provider.ts` around lines
133 - 139, Update the snapshot construction in the Pi session store provider so
header, entries, messages, currentModel, and lastUsage are deeply immutable,
including nested objects and arrays rather than only their outer containers.
Reuse or add a deep-freeze/immutable-copy helper as appropriate, and add
coverage that attempts nested mutations such as entries, message fields, and
lastUsage.cost and verifies the snapshot remains unchanged.
server/modules/providers/list/pi/pi-session-store.provider.ts-153-165 (1)

153-165: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Validate fields before casting JSON records to snapshot types.

parseHeader() accepts a version-3 header without id, timestamp, or cwd. isValidEntry() accepts an entry without timestamp. isCompleteUsage() accepts a cost object with only total.

These values are then returned as types that declare those fields required. Validate every required field, or loosen the exported types to match the accepted schema.

Also applies to: 168-175, 276-283

🤖 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/pi/pi-session-store.provider.ts` around lines
153 - 165, Update parseHeader, isValidEntry, and isCompleteUsage to validate
every field required by PiSessionHeader, entry, and usage types before casting
parsed JSON records. Require id, timestamp, and cwd for version-3 headers,
require timestamp on entries, and ensure usage cost contains all fields declared
required rather than accepting only total; alternatively loosen the exported
types to match the accepted schema.
server/modules/providers/list/pi/pi-session-store.provider.ts-116-124 (1)

116-124: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Reject a parsed but invalid final record.

The EOF exception applies to incomplete JSON. A complete JSON record can still fail isValidEntry(). The current branch silently drops that corruption when the file has no final newline.

Throw PI_SESSION_CORRUPT for every parsed record that fails validation. Add a regression test for a valid JSON EOF record with an invalid entry shape.

Proposed correction
       if (!isValidEntry(parsed)) {
-        if (isLastLine && !endsWithNewline) {
-          continue;
-        }
+        // Parsed JSON is a complete record. Reject schema failures at EOF.
         throw new AppError(`Pi session 文件损坏(行号 ${lineNumber})`, {
🤖 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/pi/pi-session-store.provider.ts` around lines
116 - 124, Update the invalid-entry handling in the Pi session parsing flow to
throw PI_SESSION_CORRUPT for every parsed record that fails isValidEntry(),
including a complete JSON record at EOF without a trailing newline. Restrict the
isLastLine && !endsWithNewline continuation to incomplete JSON handling before
validation, and add a regression test covering a valid JSON EOF record with an
invalid entry shape.
server/modules/providers/list/pi/pi-auth.provider.ts-98-100 (1)

98-100: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Wrap getAvailableModels() with a probe deadline.

RpcClient.start() has an internal timeout, but getAvailableModels() does not. If that RPC request stalls, probeAuthenticated() never settles or closes the client, so apply an application-level timeout around both calls and reject if it expires.

🤖 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/pi/pi-auth.provider.ts` around lines 98 - 100,
Update probeAuthenticated around client.start() and client.getAvailableModels()
to enforce an application-level deadline covering both operations, rejecting
when the deadline expires. Ensure timeout or RPC failures propagate through the
existing error path so the client is still closed and the probe settles.
server/modules/providers/services/provider-token-usage.service.ts-315-334 (1)

315-334: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Check that the persisted Pi transcript still exists.

A nonempty stale jsonl_path passes the guard at Line 316. The branch then calls PiTokenUsageProvider without validating the file. This does not guarantee the SESSION_FILE_NOT_FOUND response for a deleted Pi transcript.

Reject a missing path in the guard. Add a test for a nonempty path whose file is absent.

Proposed fix
-        if (!session.jsonl_path) {
+        if (!session.jsonl_path || !dependencies.fileExists(session.jsonl_path)) {
🤖 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/services/provider-token-usage.service.ts` around
lines 315 - 334, Update the Pi-session guard in the token-usage flow to verify
that the persisted file referenced by session.jsonl_path still exists, rejecting
both empty and stale paths with the existing SESSION_FILE_NOT_FOUND AppError
before calling PiTokenUsageProvider.getTokenUsage. Add a test covering a
nonempty jsonl_path whose file is absent.
server/modules/providers/list/pi/pi-models.provider.ts-88-90 (1)

88-90: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Guard the default model against an empty stateModel.

stateModel && options.find(...)?.value returns '' when getState() reports model: ''. ?? does not replace '', so DEFAULT becomes an empty string that matches no option. Use a nullish-safe lookup instead.

🛠️ Proposed fix
-    const defaultValue =
-      (stateModel && options.find((option) => option.value === stateModel)?.value) ??
-      options[0].value;
+    const matchedDefault = stateModel
+      ? options.find((option) => option.value === stateModel)?.value
+      : undefined;
+    const defaultValue = matchedDefault ?? options[0].value;
🤖 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/pi/pi-models.provider.ts` around lines 88 - 90,
Update the default model calculation in the provider’s state-model lookup so an
empty stateModel cannot become the selected default. Normalize or otherwise
treat empty stateModel as absent before applying the matching option lookup and
fallback to options[0].value, while preserving valid non-empty model selections.
server/modules/providers/list/pi/pi-models.provider.ts-79-85 (1)

79-85: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Use an English error message.

Both AppError messages use Chinese text ('Pi 未认证'). The rest of this module and the codebase use English strings. This message can reach clients and logs, so keep it consistent and translatable through the frontend locale files.

🛠️ Proposed fix
     } catch {
-      throw new AppError('Pi 未认证', { code: 'PI_NOT_AUTHENTICATED' });
+      throw new AppError('Pi is not authenticated.', { code: 'PI_NOT_AUTHENTICATED' });
     }
 
     if (rows.length === 0) {
-      throw new AppError('Pi 未认证', { code: 'PI_NOT_AUTHENTICATED' });
+      throw new AppError('Pi is not authenticated.', { code: 'PI_NOT_AUTHENTICATED' });
     }
🤖 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/pi/pi-models.provider.ts` around lines 79 - 85,
Update both AppError constructions in the Pi provider authentication flow to use
the established English message convention instead of the Chinese text, while
preserving the existing PI_NOT_AUTHENTICATED error code and control flow.
🧹 Nitpick comments (7)
server/modules/providers/list/claude/claude-runtime.provider.js (1)

212-218: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

ANTHROPIC_MODEL now overrides the explicit UI model selection.

process.env.ANTHROPIC_MODEL takes the highest precedence. When an operator sets that variable, every request runs with that model, and the model the user picked in the web UI is discarded without any feedback. resolveClaudeEffort at Line 220 then resolves effort against the environment model, not the selected model.

If the goal is only to change the default when the user makes no explicit selection, place options.model first. If the override is intentional, disable or annotate the model picker in the UI so the effective model stays visible to the user.

♻️ Proposed precedence change
-  // Prefer the locally configured model (ANTHROPIC_MODEL from Claude Code
-  // settings) so the app always matches the host CLI. When it is unset, fall
-  // back to the model picked in the web UI, then the provider catalog default.
-  sdkOptions.model =
-    process.env.ANTHROPIC_MODEL?.trim() ||
-    options.model ||
-    CLAUDE_FALLBACK_MODELS.DEFAULT;
+  // Prefer the model picked in the web UI. When the user makes no explicit
+  // selection, fall back to the locally configured model (ANTHROPIC_MODEL from
+  // Claude Code settings), then the provider catalog default.
+  sdkOptions.model =
+    options.model ||
+    process.env.ANTHROPIC_MODEL?.trim() ||
+    CLAUDE_FALLBACK_MODELS.DEFAULT;
🤖 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/claude/claude-runtime.provider.js` around lines
212 - 218, Update the model precedence in the SDK options assignment near
resolveClaudeEffort so the explicit options.model selection takes priority over
process.env.ANTHROPIC_MODEL, using the environment value only when no UI model
is selected and retaining CLAUDE_FALLBACK_MODELS.DEFAULT as the final fallback.
server/modules/providers/list/pi/pi-rpc-client.provider.test.ts (1)

223-235: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Gate the real-spawn test so the unit suite stays hermetic.

This test spawns the actual Pi CLI. It depends on the installed @earendil-works/pi-coding-agent package exposing a resolvable dist/cli.js, on a usable node binary, and on the RPC handshake finishing inside the default test timeout. Any of those conditions can fail in CI for reasons unrelated to PiRpcClient, and the failure looks like a regression in this wrapper.

Gate the test behind an explicit environment flag, or move it to a separate integration suite. Also set an explicit timeout so a stalled handshake fails fast with a clear message.

♻️ Proposed gating
-test('real spawn: default PiRpcClient resolves cli.js and receives an RPC response', async () => {
+test('real spawn: default PiRpcClient resolves cli.js and receives an RPC response', {
+  skip: process.env.PI_INTEGRATION_TESTS !== '1'
+    ? 'set PI_INTEGRATION_TESTS=1 to run the real-spawn test'
+    : false,
+  timeout: 30_000,
+}, async () => {
   const client = new PiRpcClient({});
🤖 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/pi/pi-rpc-client.provider.test.ts` around lines
223 - 235, Gate the real-spawn test around PiRpcClient.start and
getAvailableModels behind an explicit environment flag so the default unit suite
remains hermetic, while preserving its cleanup through client.close. Configure
an explicit, clearly bounded test timeout for the enabled integration check so
stalled RPC handshakes fail promptly.
server/modules/providers/list/pi/pi-runtime.provider.ts (1)

498-507: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Remove the abort listener when the run settles.

signal.addEventListener('abort', beginAbort, { once: true }) is never removed. { once: true } removes the listener only after the signal fires. When a run settles normally, the listener stays attached to the signal and retains the whole run closure, including writer, rpc, and activeThinkingBlocks.

If a caller reuses one AbortSignal across several turns of a session, each turn adds another listener, and aborting later invokes beginAbort for every completed run. Each stale call is a no-op because settled is true, but the retained closures accumulate for the lifetime of the signal.

Remove the listener inside finish.

♻️ Proposed fix
       const finish = (outcome: PiRunOutcome, closeRpc = true): void => {
         if (settled) return;
         finalizeAllThinkingBlocks();
         settled = true;
         state = 'SETTLED';
         if (abortTimer) clearTimeout(abortTimer);
+        signal?.removeEventListener('abort', beginAbort);
         activeRuns.delete(runId);

signal and beginAbort are declared after finish in the executor. Move the signal resolution and the beginAbort definition above finish, or capture the removal in a mutable cleanup callback that finish invokes.

🤖 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/pi/pi-runtime.provider.ts` around lines 498 -
507, Update the run executor’s finish path to remove the abort listener when the
run settles, including normal completion, errors, and aborts. Make signal and
beginAbort available to finish by moving their declarations earlier or using a
cleanup callback, then call signal.removeEventListener('abort', beginAbort)
before completing settlement; preserve the existing immediate-abort behavior.
server/modules/providers/list/pi/pi-runtime.provider.test.ts (1)

641-642: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Await client.start() in T28.

The assertion depends on createClient running synchronously inside start() before the first await. That holds today, but any future await placed ahead of createClient in PiRpcClient.start() makes this assertion read an empty captured array. void also discards a rejection. Await the promise so the test stays valid and reports failures.

♻️ Proposed change
-test('T28: default RPC client spawns with --no-extensions', () => {
+test('T28: default RPC client spawns with --no-extensions', async () => {
-  void client.start();
+  await client.start();
   assert.ok(captured.includes('--no-extensions'), 'runtime spawn flags include --no-extensions');
🤖 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/pi/pi-runtime.provider.test.ts` around lines
641 - 642, Update the T28 test to await the promise returned by client.start()
before asserting captured runtime flags. Replace the void invocation so start
failures propagate through the test and the assertion remains valid if
PiRpcClient.start() becomes asynchronous before createClient.
server/modules/providers/tests/provider-token-usage.service.test.ts (1)

174-223: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Exercise the latest-snapshot behavior.

Lines 179-205 write one usage snapshot. An implementation that returns the first snapshot would pass this test. Add a distinct earlier usage snapshot and keep the assertion on the later values.

🤖 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/tests/provider-token-usage.service.test.ts` around
lines 174 - 223, Update the T22 test in “Pi token usage returns the last valid
usage snapshot” to write two valid assistant usage messages with distinct token
values, placing the asserted values in the later snapshot. Keep the existing
expected assertion unchanged so the test specifically verifies that
getSessionTokenUsage returns the latest snapshot rather than the first.
server/modules/websocket/tests/shell-websocket.service.test.ts (1)

121-150: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for the Pi resume branch.

This test covers only the fresh-session path, where resolveProviderSessionId returns null. The pi --session "<id>" branch at shell-websocket.service.ts lines 220-222 stays untested. That branch builds the resume id into the spawned command, so it is the branch most worth asserting.

🧪 Suggested additional test
test('Pi agent terminals resume with the resolved session id', () => {
  const pty = createFakePty();
  const socket = createFakeSocket();
  let spawnedArguments: string[] = [];
  const dependencies = {
    resolveProviderSessionId: () => 'pi-resume-123',
    spawnPty: (_shell: string, args: string | string[]) => {
      spawnedArguments = typeof args === 'string' ? [args] : args;
      return pty as never;
    },
  };

  handleShellConnection(socket as never, dependencies);
  socket.emit(
    'message',
    JSON.stringify({
      type: 'init',
      projectPath: process.cwd(),
      sessionId: 'pi-resume-123',
      hasSession: true,
      provider: 'pi',
    }),
  );

  assert.match(spawnedArguments.join(' '), /pi --session "pi-resume-123"/);
  assert.ok(socket.frames.some((frame) => frame.includes('Resuming Pi session')));

  pty.emitExit();
});
🤖 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/websocket/tests/shell-websocket.service.test.ts` around lines
121 - 150, Add a test alongside the existing Pi fresh-session test that
configures resolveProviderSessionId to return a session ID and initializes a Pi
connection with hasSession true. Assert the spawned command includes the
resolved ID in the pi --session branch and that the socket reports resuming the
Pi session, then emit the PTY exit to preserve cleanup.
server/modules/websocket/services/shell-websocket.service.ts (1)

220-222: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider a fallback when the Pi resume fails.

The codex and claude branches fall back to a fresh session when the resume command exits non-zero (codex resume "<id>" || codex). The pi branch returns only pi --session "<id>". If the session id no longer exists in Pi, the terminal shows an error and stops. cursor and opencode have the same gap, so treat this as optional consistency work.

🤖 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/websocket/services/shell-websocket.service.ts` around lines
220 - 222, Update the resume-command construction in the shell service so the pi
branch falls back to a fresh pi session when resuming the specified session
exits non-zero, matching the existing codex and claude behavior. Optionally
apply the same fallback pattern to the cursor and opencode branches, while
preserving normal resume behavior when the session exists.
🤖 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 `@openspec/changes/add-pi-provider/design.md`:
- Around line 57-59: Update openspec/changes/add-pi-provider/design.md lines
57-59 so every Pi MCP operation, including read/list and write operations,
returns ERR-PROVIDER-CAPABILITY-UNSUPPORTED instead of an empty successful read
result. Update openspec/changes/add-pi-provider/test-definition.md line 62 to
add a read/list MCP test that requires the same unsupported error, while
retaining write coverage.

In `@openspec/changes/add-pi-provider/test-definition.md`:
- Around line 23-29: Update the normal-path coverage table in the test
definition to include nine samples, adding named entries for the
installed-and-authenticated status scenario and Pi session synchronizer
discovery with failure isolation. Add corresponding acceptance-gate entries that
verify both behaviors, while preserving the existing sample counts and coverage
categories.

In `@openspec/changes/refactor-provider-seams/design.md`:
- Around line 54-55: 明确 provider-qualified 唯一性与迁移规则:在
openspec/changes/refactor-provider-seams/design.md(54-55、124-125)规定迁移前同 provider
重复行的确定性 survivor、关联记录外键重映射、history 保留及冲突处理,并要求单事务完成;在
openspec/changes/refactor-provider-seams/specs/provider-seams/spec.md(49-51)规定未来同
provider 重复写入的拒绝或显式 upsert行为,且不同 provider 的相同 native ID 必须保留为两行;在
openspec/changes/refactor-provider-seams/tasks.md(38-39)加入 provider_session_id
IS NOT NULL 过滤、事务边界及 provider-qualified merge 断言;在
openspec/changes/refactor-provider-seams/test-definition.md(35-43)补充 R6 同
provider 规则和 R14 跨 provider 保留两行的验证。

In `@openspec/changes/refactor-provider-seams/specs/provider-seams/spec.md`:
- Around line 21-37: 统一错误码契约:在
openspec/changes/refactor-provider-seams/specs/provider-seams/spec.md:21-37
明确区分需求 ID 与实际返回字段和值;在 openspec/changes/refactor-provider-seams/design.md:91-98
统一 ERR 标识、AppError.code 与 HTTP 状态码的映射;在
openspec/changes/refactor-provider-seams/test-definition.md:30-33 断言实际返回字段及其精确
code 值,而非仅断言 ERR-* 别名。

In `@openspec/changes/refactor-provider-seams/test-definition.md`:
- Around line 57-59: 将“边界”覆盖率要求从“≥ 90%”提高为“100%”,确保规范性场景 R7 和 R9 必须全部通过;保留该行对
R7、R9 等场景的引用,不降低其他类别的现有门槛。

In `@server/modules/providers/list/pi/pi-auth.provider.ts`:
- Around line 77-80: Replace the synchronous spawnSync version probe in
checkInstalled(), used by getStatus(), with an asynchronous child-process call
that preserves the existing pi --version arguments and applies
VERSION_TIMEOUT_MS. Update the surrounding call chain as needed so the async
result is awaited without blocking the event loop.

In `@server/modules/providers/list/pi/pi-rpc-client.provider.ts`:
- Around line 78-91: Update the onClose handler around RpcClient.process to log
a warning when the child process field is unavailable, and add a startup
assertion or bounded watchdog in the surrounding runtime so missing exit
notifications cannot leave the request unresolved. Preserve the existing exit
listener cleanup and ERR-PI-RUN-FAILED behavior when the process is available.

In `@server/modules/providers/list/pi/pi-session-store.provider.ts`:
- Around line 82-85: Update PiSessionStoreProvider.load and the callers
PiSessionsProvider.fetchHistory and PiTokenUsageProvider.getTokenUsage to parse
session files off the Node.js event loop using an asynchronous bounded/streaming
parser or worker-based implementation. Avoid full-file synchronous reads,
splitting, or JSON.parse on the main thread, and preserve the existing
PiSessionSnapshot behavior.

In `@server/modules/providers/list/pi/pi-session-synchronizer.provider.ts`:
- Around line 33-43: The Pi session synchronization path currently performs
synchronous file reads and JSONL parsing during scanning. Update
PiSessionStore.load and the loop in the session synchronizer to use an
asynchronous, non-blocking load/parse path, await the result before incrementing
processed, and preserve per-file error handling; alternatively remove this
synchronizer from HTTP/WS execution if that is the established design.

In `@server/modules/providers/list/pi/pi.provider.ts`:
- Around line 42-54: Update withProbe so client.start() executes inside the
try/finally lifecycle, ensuring client.close(MODELS_PROBE_GRACE_MS) runs when
startup rejects. Pass client directly to fn without the unknown-to-PiModelsProbe
cast, preserving type checking through PiRpcClient’s exposed probe methods.

In `@src/components/settings/view/tabs/agents-settings/AgentsSettingsTab.tsx`:
- Around line 25-26: The Pi tab list exposes permissions without corresponding
content, resulting in an empty tab. Update the selectedAgent handling in
AgentsSettingsTab so Pi permissions is only exposed once
AgentCategoryContentSection supports a Pi renderer; preferably add the Pi
permissions content path there, otherwise remove permissions from Pi’s returned
categories until implemented.

---

Minor comments:
In `@docs/pi-provider-integration-plan.md`:
- Around line 3-7: Update the Pi implementation status documentation: in
docs/pi-provider-integration-plan.md lines 3-7, mark the plan implemented or
historical; in TODO.md lines 3-4, remove the completed Pi integration task or
replace it with remaining work; and in openspec/changes/add-pi-provider/tasks.md
lines 21-61, mark completed tasks or identify the checklist as historical,
clearly reflecting the delivered backend and frontend behavior.

In `@openspec/changes/refactor-provider-seams/test-definition.md`:
- Line 20: Update the normal-path entry in the test-definition table so the
stated sample count matches the described five capability-contract scenarios
plus four provider characterization smoke scenarios, or revise the description
to explicitly define the grouping represented by the count.

In `@server/modules/providers/list/pi/pi-auth.provider.ts`:
- Around line 98-100: Update probeAuthenticated around client.start() and
client.getAvailableModels() to enforce an application-level deadline covering
both operations, rejecting when the deadline expires. Ensure timeout or RPC
failures propagate through the existing error path so the client is still closed
and the probe settles.

In `@server/modules/providers/list/pi/pi-models.provider.ts`:
- Around line 88-90: Update the default model calculation in the provider’s
state-model lookup so an empty stateModel cannot become the selected default.
Normalize or otherwise treat empty stateModel as absent before applying the
matching option lookup and fallback to options[0].value, while preserving valid
non-empty model selections.
- Around line 79-85: Update both AppError constructions in the Pi provider
authentication flow to use the established English message convention instead of
the Chinese text, while preserving the existing PI_NOT_AUTHENTICATED error code
and control flow.

In `@server/modules/providers/list/pi/pi-session-store.provider.ts`:
- Around line 133-139: Update the snapshot construction in the Pi session store
provider so header, entries, messages, currentModel, and lastUsage are deeply
immutable, including nested objects and arrays rather than only their outer
containers. Reuse or add a deep-freeze/immutable-copy helper as appropriate, and
add coverage that attempts nested mutations such as entries, message fields, and
lastUsage.cost and verifies the snapshot remains unchanged.
- Around line 153-165: Update parseHeader, isValidEntry, and isCompleteUsage to
validate every field required by PiSessionHeader, entry, and usage types before
casting parsed JSON records. Require id, timestamp, and cwd for version-3
headers, require timestamp on entries, and ensure usage cost contains all fields
declared required rather than accepting only total; alternatively loosen the
exported types to match the accepted schema.
- Around line 116-124: Update the invalid-entry handling in the Pi session
parsing flow to throw PI_SESSION_CORRUPT for every parsed record that fails
isValidEntry(), including a complete JSON record at EOF without a trailing
newline. Restrict the isLastLine && !endsWithNewline continuation to incomplete
JSON handling before validation, and add a regression test covering a valid JSON
EOF record with an invalid entry shape.

In `@server/modules/providers/services/provider-token-usage.service.ts`:
- Around line 315-334: Update the Pi-session guard in the token-usage flow to
verify that the persisted file referenced by session.jsonl_path still exists,
rejecting both empty and stale paths with the existing SESSION_FILE_NOT_FOUND
AppError before calling PiTokenUsageProvider.getTokenUsage. Add a test covering
a nonempty jsonl_path whose file is absent.

In `@src/components/onboarding/view/subcomponents/AgentConnectionsStep.tsx`:
- Around line 49-55: Keep an unauthenticated Pi login entry point reachable:
update the Pi configuration in AgentConnectionsStep.tsx (lines 49-55) by
removing hideLogin or adding a visible custom action that invokes
onOpenProviderLogin. Apply the equivalent reachability fix to the Pi entry in
AccountContent.tsx (line 123), if it uses the same hidden-login configuration.

---

Nitpick comments:
In `@server/modules/providers/list/claude/claude-runtime.provider.js`:
- Around line 212-218: Update the model precedence in the SDK options assignment
near resolveClaudeEffort so the explicit options.model selection takes priority
over process.env.ANTHROPIC_MODEL, using the environment value only when no UI
model is selected and retaining CLAUDE_FALLBACK_MODELS.DEFAULT as the final
fallback.

In `@server/modules/providers/list/pi/pi-rpc-client.provider.test.ts`:
- Around line 223-235: Gate the real-spawn test around PiRpcClient.start and
getAvailableModels behind an explicit environment flag so the default unit suite
remains hermetic, while preserving its cleanup through client.close. Configure
an explicit, clearly bounded test timeout for the enabled integration check so
stalled RPC handshakes fail promptly.

In `@server/modules/providers/list/pi/pi-runtime.provider.test.ts`:
- Around line 641-642: Update the T28 test to await the promise returned by
client.start() before asserting captured runtime flags. Replace the void
invocation so start failures propagate through the test and the assertion
remains valid if PiRpcClient.start() becomes asynchronous before createClient.

In `@server/modules/providers/list/pi/pi-runtime.provider.ts`:
- Around line 498-507: Update the run executor’s finish path to remove the abort
listener when the run settles, including normal completion, errors, and aborts.
Make signal and beginAbort available to finish by moving their declarations
earlier or using a cleanup callback, then call
signal.removeEventListener('abort', beginAbort) before completing settlement;
preserve the existing immediate-abort behavior.

In `@server/modules/providers/tests/provider-token-usage.service.test.ts`:
- Around line 174-223: Update the T22 test in “Pi token usage returns the last
valid usage snapshot” to write two valid assistant usage messages with distinct
token values, placing the asserted values in the later snapshot. Keep the
existing expected assertion unchanged so the test specifically verifies that
getSessionTokenUsage returns the latest snapshot rather than the first.

In `@server/modules/websocket/services/shell-websocket.service.ts`:
- Around line 220-222: Update the resume-command construction in the shell
service so the pi branch falls back to a fresh pi session when resuming the
specified session exits non-zero, matching the existing codex and claude
behavior. Optionally apply the same fallback pattern to the cursor and opencode
branches, while preserving normal resume behavior when the session exists.

In `@server/modules/websocket/tests/shell-websocket.service.test.ts`:
- Around line 121-150: Add a test alongside the existing Pi fresh-session test
that configures resolveProviderSessionId to return a session ID and initializes
a Pi connection with hasSession true. Assert the spawned command includes the
resolved ID in the pi --session branch and that the socket reports resuming the
Pi session, then emit the PTY exit to preserve cleanup.
🪄 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 Plus

Run ID: e894c365-4981-42e9-b23d-d49f1fb20e4a

📥 Commits

Reviewing files that changed from the base of the PR and between f0dca2d and 6c5089b.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (106)
  • .gitignore
  • CONTEXT.md
  • TODO.md
  • docs/paseo-agent-switching-analysis.md
  • docs/paseo-provider-switch-design.md
  • docs/pi-provider-integration-plan.md
  • openspec/changes/add-pi-provider/.openspec.yaml
  • openspec/changes/add-pi-provider/design.md
  • openspec/changes/add-pi-provider/proposal.md
  • openspec/changes/add-pi-provider/specs/pi-provider/spec.md
  • openspec/changes/add-pi-provider/tasks.md
  • openspec/changes/add-pi-provider/test-definition.md
  • openspec/changes/refactor-provider-seams/.openspec.yaml
  • openspec/changes/refactor-provider-seams/design.md
  • openspec/changes/refactor-provider-seams/proposal.md
  • openspec/changes/refactor-provider-seams/specs/provider-seams/spec.md
  • openspec/changes/refactor-provider-seams/tasks.md
  • openspec/changes/refactor-provider-seams/test-definition.md
  • openspec/config.yaml
  • package.json
  • server/index.ts
  • server/modules/agent/agent.module.ts
  • server/modules/agent/agent.routes.ts
  • server/modules/agent/tests/agent.routes.test.ts
  • server/modules/commands/commands.routes.ts
  • server/modules/commands/tests/commands.test.ts
  • server/modules/providers/list/claude/claude-runtime.provider.js
  • server/modules/providers/list/pi/index.ts
  • server/modules/providers/list/pi/pi-auth.provider.test.ts
  • server/modules/providers/list/pi/pi-auth.provider.ts
  • server/modules/providers/list/pi/pi-mcp.provider.test.ts
  • server/modules/providers/list/pi/pi-mcp.provider.ts
  • server/modules/providers/list/pi/pi-models.provider.test.ts
  • server/modules/providers/list/pi/pi-models.provider.ts
  • server/modules/providers/list/pi/pi-paths.provider.test.ts
  • server/modules/providers/list/pi/pi-paths.provider.ts
  • server/modules/providers/list/pi/pi-rpc-client.provider.test.ts
  • server/modules/providers/list/pi/pi-rpc-client.provider.ts
  • server/modules/providers/list/pi/pi-runtime.provider.test.ts
  • server/modules/providers/list/pi/pi-runtime.provider.ts
  • server/modules/providers/list/pi/pi-session-store.provider.test.ts
  • server/modules/providers/list/pi/pi-session-store.provider.ts
  • server/modules/providers/list/pi/pi-session-synchronizer.provider.test.ts
  • server/modules/providers/list/pi/pi-session-synchronizer.provider.ts
  • server/modules/providers/list/pi/pi-sessions.provider.test.ts
  • server/modules/providers/list/pi/pi-sessions.provider.ts
  • server/modules/providers/list/pi/pi-skills.provider.test.ts
  • server/modules/providers/list/pi/pi-skills.provider.ts
  • server/modules/providers/list/pi/pi-token-usage.provider.test.ts
  • server/modules/providers/list/pi/pi-token-usage.provider.ts
  • server/modules/providers/list/pi/pi.provider.test.ts
  • server/modules/providers/list/pi/pi.provider.ts
  • server/modules/providers/provider.registry.ts
  • server/modules/providers/provider.routes.ts
  • server/modules/providers/services/mcp.service.ts
  • server/modules/providers/services/provider-capabilities.service.ts
  • server/modules/providers/services/provider-token-usage.service.ts
  • server/modules/providers/services/session-synchronizer.service.ts
  • server/modules/providers/services/sessions-watcher.service.ts
  • server/modules/providers/services/sessions.service.ts
  • server/modules/providers/tests/mcp.test.ts
  • server/modules/providers/tests/provider-capabilities.service.test.ts
  • server/modules/providers/tests/provider-registry.test.ts
  • server/modules/providers/tests/provider-runtime.service.test.ts
  • server/modules/providers/tests/provider-token-usage.service.test.ts
  • server/modules/providers/tests/provider.routes.test.ts
  • server/modules/providers/tests/sessions-watcher-paths.test.ts
  • server/modules/websocket/services/shell-websocket.service.ts
  • server/modules/websocket/tests/shell-websocket.service.test.ts
  • server/shared/types.ts
  • src/components/auth/types.ts
  • src/components/auth/utils.ts
  • src/components/chat/constants/providerEffort.ts
  • src/components/chat/hooks/useChatComposerState.ts
  • src/components/chat/hooks/useChatMessages.test.ts
  • src/components/chat/hooks/useChatMessages.ts
  • src/components/chat/hooks/useChatProviderState.ts
  • src/components/chat/types/types.ts
  • src/components/chat/view/ChatInterface.tsx
  • src/components/chat/view/subcomponents/ChatMessagesPane.tsx
  • src/components/chat/view/subcomponents/CommandResultModal.tsx
  • src/components/chat/view/subcomponents/MessageComponent.tsx
  • src/components/chat/view/subcomponents/ProviderSelectionEmptyState.tsx
  • src/components/llm-logo-provider/PiLogo.tsx
  • src/components/llm-logo-provider/SessionProviderLogo.tsx
  • src/components/mcp/types.ts
  • src/components/onboarding/view/subcomponents/AgentConnectionCard.tsx
  • src/components/onboarding/view/subcomponents/AgentConnectionsStep.tsx
  • src/components/provider-auth/hooks/useProviderAuthStatus.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/AgentCategoryContentSection.tsx
  • src/components/settings/view/tabs/agents-settings/sections/AgentSelectorSection.tsx
  • src/components/settings/view/tabs/agents-settings/sections/content/AccountContent.tsx
  • src/components/skills/view/ProviderSkills.tsx
  • src/i18n/locales/en/chat.json
  • src/i18n/locales/en/settings.json
  • src/i18n/locales/fr/chat.json
  • src/i18n/locales/fr/settings.json
  • src/stores/sessionMessageReconciliation.test.ts
  • src/stores/sessionMessageReconciliation.ts
  • src/stores/useSessionStore.ts
  • src/types/app.ts

Comment on lines +57 to +59
**决策 3:Pi 提供「不支持」语义的 mcp facet,而非把 mcp 改成 optional。**
理由:`IProvider` 当前强制 mcp(E2),改成 optional 属于被切掉的重构。Pi 的 mcp facet 读操作返回完整的分组空结构,写操作抛 `ERR-PROVIDER-CAPABILITY-UNSUPPORTED`。capabilities 矩阵里 Pi 手写 `supportsMcp:false`。

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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Return the unsupported error for every Pi MCP operation.

The design returns an empty successful result for Pi MCP reads. The normative spec requires ERR-PROVIDER-CAPABILITY-UNSUPPORTED for unsupported Pi MCP requests. The current test only verifies writes, so it permits the invalid read behavior.

  • openspec/changes/add-pi-provider/design.md#L57-L59: Make read and write MCP operations return ERR-PROVIDER-CAPABILITY-UNSUPPORTED.
  • openspec/changes/add-pi-provider/test-definition.md#L62-L62: Add a read/list MCP test that requires the same error instead of an empty result.
📍 Affects 2 files
  • openspec/changes/add-pi-provider/design.md#L57-L59 (this comment)
  • openspec/changes/add-pi-provider/test-definition.md#L62-L62
🤖 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 `@openspec/changes/add-pi-provider/design.md` around lines 57 - 59, Update
openspec/changes/add-pi-provider/design.md lines 57-59 so every Pi MCP
operation, including read/list and write operations, returns
ERR-PROVIDER-CAPABILITY-UNSUPPORTED instead of an empty successful read result.
Update openspec/changes/add-pi-provider/test-definition.md line 62 to add a
read/list MCP test that requires the same unsupported error, while retaining
write coverage.

Comment on lines +23 to +29
| 维度 | 是否覆盖 | 样本数 | 说明 |
|---|---|---|---|
| 正常路径 | 是 | 9 | 每条 spec 需求的正常场景各一 |
| 异常 | 是 | 8 | 进程早关、协议错误、未认证、未安装、中间损坏、不支持版本、优雅超时、无 usage |
| 边界 | 是 | 5 | 单 chunk 多行、一行跨多 chunk、尾部半行、active leaf 回溯、最后 model_change |
| 对抗 | 是 | 4 | 畸形 JSON、stderr 污染 stdout parser、未知事件、已知事件非法 payload |
| 高风险 | 是 | 4 | abort 与 late native event 竞争仅一个终态、绑定先于首事件、进程 unexpected close reject pending、`--no-extensions` probe/runtime 一致 |

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.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Add the missing required-path tests.

The table claims nine normal-path samples, but it enumerates eight. It also omits tests for the installed-and-authenticated status scenario and Pi session synchronizer discovery and failure isolation. Add named samples and acceptance-gate entries for these required behaviors.

🤖 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 `@openspec/changes/add-pi-provider/test-definition.md` around lines 23 - 29,
Update the normal-path coverage table in the test definition to include nine
samples, adding named entries for the installed-and-authenticated status
scenario and Pi session synchronizer discovery with failure isolation. Add
corresponding acceptance-gate entries that verify both behaviors, while
preserving the existing sample counts and coverage categories.

Comment on lines +54 to +55
**决策 4:native 身份加 `(provider, provider_session_id)` 唯一约束 + provider-qualified lookup/merge。**
替代方案:仅靠 UUID 唯一性(即 add-pi-provider 现状)——被否,那是临时兜底;本 change 的职责就是根治(E7)。迁移前必须先合并真实库中的重复行(E12)。

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.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

明确同 provider 重复 ID 的迁移规则和未来写入规则。

当前文档允许“拒绝或确定性合并”,但没有定义 survivor、外键重映射、history 保留和冲突处理。R14 还把跨 provider 的相同 native ID 描述为“provider-qualified 合并”。这不能形成可执行的数据安全测试。请明确:

  • 迁移前同 provider 重复行如何确定性合并。

  • 迁移如何重映射关联记录,并在单事务内完成。

  • 未来同 provider 重复写入是拒绝还是显式 upsert。

  • 不同 provider 的相同 native ID 必须保留为两行,不能合并。

  • 重复查询必须使用 WHERE provider_session_id IS NOT NULL,否则所有未绑定 native ID 的行可能被当成同一组。

  • openspec/changes/refactor-provider-seams/design.md#L54-L55: 定义唯一约束、迁移 survivor 和关联记录处理。

  • openspec/changes/refactor-provider-seams/design.md#L124-L125: 将迁移风险转化为具体的合并和验证步骤。

  • openspec/changes/refactor-provider-seams/specs/provider-seams/spec.md#L49-L51: 将迁移重复、未来写入和拒绝规则拆成独立规范。

  • openspec/changes/refactor-provider-seams/tasks.md#L38-L39: 添加非空过滤、事务边界和 provider-qualified merge 断言。

  • openspec/changes/refactor-provider-seams/test-definition.md#L35-L43: R6 验证同 provider 规则,R14 验证跨 provider 仍有两行。

📍 Affects 4 files
  • openspec/changes/refactor-provider-seams/design.md#L54-L55 (this comment)
  • openspec/changes/refactor-provider-seams/design.md#L124-L125
  • openspec/changes/refactor-provider-seams/specs/provider-seams/spec.md#L49-L51
  • openspec/changes/refactor-provider-seams/tasks.md#L38-L39
  • openspec/changes/refactor-provider-seams/test-definition.md#L35-L43
🤖 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 `@openspec/changes/refactor-provider-seams/design.md` around lines 54 - 55, 明确
provider-qualified 唯一性与迁移规则:在
openspec/changes/refactor-provider-seams/design.md(54-55、124-125)规定迁移前同 provider
重复行的确定性 survivor、关联记录外键重映射、history 保留及冲突处理,并要求单事务完成;在
openspec/changes/refactor-provider-seams/specs/provider-seams/spec.md(49-51)规定未来同
provider 重复写入的拒绝或显式 upsert行为,且不同 provider 的相同 native ID 必须保留为两行;在
openspec/changes/refactor-provider-seams/tasks.md(38-39)加入 provider_session_id
IS NOT NULL 过滤、事务边界及 provider-qualified merge 断言;在
openspec/changes/refactor-provider-seams/test-definition.md(35-43)补充 R6 同
provider 规则和 R14 跨 provider 保留两行的验证。

Comment on lines +21 to +37
### Requirement: 未注册 provider 与不支持能力的错误码区分

The system SHALL 对"未注册的 provider"返回 `ERR-UNSUPPORTED-PROVIDER`,对"已注册但缺少某 facet"返回 `ERR-PROVIDER-CAPABILITY-UNSUPPORTED`,二者为不同的稳定错误。

The system SHALL NOT 用空成功结果把"不支持"伪装成"支持但无数据"。

#### Scenario: 未注册 provider
- **WHEN** 调用方以一个未注册的 provider id 请求任意 facet
- **THEN** 系统以 `ERR-UNSUPPORTED-PROVIDER` 拒绝

#### Scenario: 已注册但 facet 不支持
- **WHEN** 调用方对一个已注册 provider 请求其未提供的 facet
- **THEN** 系统以 `ERR-PROVIDER-CAPABILITY-UNSUPPORTED` 拒绝,而非返回空成功

#### Scenario: 注册时 descriptor 非法
- **WHEN** 注册一个默认权限模式不在其权限模式列表中的 provider
- **THEN** 系统在注册阶段以 `ERR-PROVIDER-DESCRIPTOR-INVALID` 拒绝,不进入可用集合

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.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

统一稳定的 wire error code。

spec.mdtest-definition.md 使用 ERR-* 标识符作为返回的错误码,但 design.md 将相同标识符映射为不带 ERR-AppError.code 值。请明确 ERR-* 是需求 ID 还是实际返回字段,并在 spec 和测试中使用精确的字段和值。

  • openspec/changes/refactor-provider-seams/specs/provider-seams/spec.md#L21-L37: 区分需求 ID 与返回的 code
  • openspec/changes/refactor-provider-seams/design.md#L91-L98: 统一 ERR ID、AppError.code 和 HTTP 状态码的命名。
  • openspec/changes/refactor-provider-seams/test-definition.md#L30-L33: 断言实际返回字段和值,不要只断言 ERR-* 别名。
📍 Affects 3 files
  • openspec/changes/refactor-provider-seams/specs/provider-seams/spec.md#L21-L37 (this comment)
  • openspec/changes/refactor-provider-seams/design.md#L91-L98
  • openspec/changes/refactor-provider-seams/test-definition.md#L30-L33
🤖 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 `@openspec/changes/refactor-provider-seams/specs/provider-seams/spec.md` around
lines 21 - 37, 统一错误码契约:在
openspec/changes/refactor-provider-seams/specs/provider-seams/spec.md:21-37
明确区分需求 ID 与实际返回字段和值;在 openspec/changes/refactor-provider-seams/design.md:91-98
统一 ERR 标识、AppError.code 与 HTTP 状态码的映射;在
openspec/changes/refactor-provider-seams/test-definition.md:30-33 断言实际返回字段及其精确
code 值,而非仅断言 ERR-* 别名。

Comment on lines +57 to +59
| 契约异常全通过 | 100% | R2、R3、R4、R6、R12(错误码精确匹配) |
| 单一终态 | 100% | R10、R11、R12、R13 |
| 边界 | ≥ 90% | R7、R9 等 |

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.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

对规范性边界场景要求 100% 通过。

spec.md 将 R7 和 R9 定义为 SHALL 行为。当前 ≥ 90% 门槛允许一个规范性场景失败后仍然放行。请将 R7、R9 的门槛设为 100%。较低门槛只适用于非规范性的诊断测试。

建议修改
-| 边界 | ≥ 90% | R7、R9 等 |
+| 边界 | 100% | R7、R9 |
📝 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
| 契约异常全通过 | 100% | R2、R3、R4、R6、R12(错误码精确匹配) |
| 单一终态 | 100% | R10、R11、R12、R13 |
| 边界 | ≥ 90% | R7、R9 |
| 契约异常全通过 | 100% | R2、R3、R4、R6、R12(错误码精确匹配) |
| 单一终态 | 100% | R10、R11、R12、R13 |
| 边界 | 100% | R7、R9 |
🤖 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 `@openspec/changes/refactor-provider-seams/test-definition.md` around lines 57
- 59, 将“边界”覆盖率要求从“≥ 90%”提高为“100%”,确保规范性场景 R7 和 R9 必须全部通过;保留该行对 R7、R9
等场景的引用,不降低其他类别的现有门槛。

Comment on lines +78 to +91
onClose: (listener) => {
// The official client exposes its spawned ChildProcess as a public
// field once start() has run. Forward its `exit` to the runtime so a
// process death before agent_settled surfaces as a failure.
// `process` is a runtime-public field; the shipped .d.ts marks it
// private, so reach it through a narrow typed view.
const child = (client as unknown as { process: ChildProcess | null }).process;
if (!child) return () => {};
const onExit = (): void => listener();
child.once('exit', onExit);
return () => {
child.removeListener('exit', onExit);
};
},

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.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

pkg=$(fd -t d -H 'pi-coding-agent' node_modules 2>/dev/null | head -n 1 || true)
if [ -z "$pkg" ]; then
  echo "package not installed in sandbox; verify in an environment with dependencies installed"
  exit 0
fi

fd -e d.ts -e ts . "$pkg" --exec rg -n -C3 '\bprocess\b.*ChildProcess|class RpcClient' {} \;

Repository: siteboon/claudecodeui

Length of output: 246


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== repo files matching target =="
fd -a 'pi-rpc-client\.provider\.ts|pi-runtime\.provider\.ts$' . || true

echo "== backend skill =="
if [ -f ".agents/skills/backend-module-standards/SKILL.md" ]; then
  wc -l ".agents/skills/backend-module-standards/SKILL.md"
  sed -n '1,220p' ".agents/skills/backend-module-standards/SKILL.md"
else
  echo "backend skill missing"
fi

echo "== rpc client outline and relevant lines =="
file=$(fd 'pi-rpc-client\.provider\.ts' . | head -n 1 || true)
if [ -n "$file" ]; then
  wc -l "$file"
  ast-grep outline "$file" --match PiRpcClientProvider --view expanded || true
  sed -n '1,140p' "$file"
fi

echo "== runtime relevant lines =="
runtime=$(fd 'pi-runtime\.provider\.ts' . | head -n 1 || true)
if [ -n "$runtime" ]; then
  wc -l "$runtime"
  sed -n '540,625p' "$runtime"
fi

echo "== package references to pi-coding-agent and local lock files =="
rg -n '"`@pi-coding-agent/client`"|"pi-coding-agent|rpc\.onClose|agent_settled|ERR-PI-RUN-FAILED' --glob '!node_modules/**' --glob '!dist/**' --glob '!build/**' .

Repository: siteboon/claudecodeui

Length of output: 22244


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== RpcClient imports and full PiRpcClient class =="
rpc_file=$(fd 'pi-rpc-client\.provider\.ts' . | head -n 1 || true)
if [ -n "$rpc_file" ]; then
  sed -n '1,120p' "$rpc_file"
  sed -n '120,200p' "$rpc_file"
fi

echo "== lockfile/package exact `@earendil-works/pi-coding-agent` versions =="
rg -n '"`@earendil-works/pi-coding-agent`"|(`@earendil-works/pi-coding-agent`@)' package.json package-lock.json bun.lockb npm-shrinkwrap.json pnpm-lock.yaml yarn.lock 2>/dev/null || true

echo "== runtime file size and state/finish helpers =="
runtime=$(fd 'pi-runtime\.provider\.ts' . | head -n 1 || true)
if [ -n "$runtime" ]; then
  wc -l "$runtime"
  sed -n '1,120p' "$runtime"
  sed -n '440,510p' "$runtime"
  sed -n '560,635p' "$runtime"
fi

echo "== runtime tests around process close =="
t=$(fd 'pi-runtime\.provider\.test\.ts' . | head -n 1 || true)
if [ -n "$t" ]; then
  sed -n '420,460p' "$t"
fi

Repository: siteboon/claudecodeui

Length of output: 17722


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== runtime main entry and resolve loop =="
runtime=$(fd 'pi-runtime\.provider\.ts' . | head -n 1 || true)
sed -n '360,460p' "$runtime"
sed -n '480,505p' "$runtime"

echo "== check installed package source if present =="
pkg=$(fd -t d -H 'pi-coding-agent' node_modules 2>/dev/null | head -n 1 || true)
if [ -n "$pkg" ]; then
  echo "found package: $pkg"
  rg -n -C3 'class RpcClient|process:|onClose|process' "$pkg" --glob '!node_modules/**'
fi

echo "== installed declarations for RpcClient process if present =="
if [ -n "$pkg" ]; then
  fd -e d.ts . "$pkg" --exec sed -n
fi

Repository: siteboon/claudecodeui

Length of output: 4457


Add a watchdog when RpcClient.process is unavailable.

If the shipped package changes and client.process is absent, onClose returns a no-op subscription. The runtime only returns ERR-PI-RUN-FAILED from rpc.onClose, so a Pi process death before agent_settled can leave the run unresolved. Log a warning when the field is missing, and add a startup assertion or bounded watchdog timeout in the runtime so a missing exit signal cannot hang the request.

🤖 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/pi/pi-rpc-client.provider.ts` around lines 78 -
91, Update the onClose handler around RpcClient.process to log a warning when
the child process field is unavailable, and add a startup assertion or bounded
watchdog in the surrounding runtime so missing exit notifications cannot leave
the request unresolved. Preserve the existing exit listener cleanup and
ERR-PI-RUN-FAILED behavior when the process is available.

Comment on lines +82 to +85
load(filePath: string): PiSessionSnapshot {
const raw = readFileSync(filePath, 'utf8');
const endsWithNewline = raw.endsWith('\n');
const lines = raw.split('\n');

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.

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Move session parsing off the Node.js event loop.

PiSessionsProvider.fetchHistory() and PiTokenUsageProvider.getTokenUsage() call this loader. readFileSync(), split(), and JSON.parse() process the complete JSONL file synchronously. A large session or slow disk stalls unrelated server work.

Use an asynchronous bounded parser or a worker-based parser. Do not only replace readFileSync(), because complete-file splitting and parsing still block the event loop.

🤖 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/pi/pi-session-store.provider.ts` around lines
82 - 85, Update PiSessionStoreProvider.load and the callers
PiSessionsProvider.fetchHistory and PiTokenUsageProvider.getTokenUsage to parse
session files off the Node.js event loop using an asynchronous bounded/streaming
parser or worker-based implementation. Avoid full-file synchronous reads,
splitting, or JSON.parse on the main thread, and preserve the existing
PiSessionSnapshot behavior.

Comment on lines +33 to +43
for (const filePath of files) {
try {
const sessionId = this.indexFile(filePath);
if (sessionId) {
processed += 1;
}
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
console.warn('[PiProvider] Failed to synchronize session file:', filePath, message);
}
}

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.

🚀 Performance & Scalability | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Determine whether PiSessionStore.load uses synchronous filesystem IO.
fd -t f 'pi-session-store.provider.ts' server | xargs -r ast-grep outline --items all
fd -t f 'pi-session-store.provider.ts' server | xargs -r rg -n -C3 'readFileSync|readFile|statSync|createReadStream'

Repository: siteboon/claudecodeui

Length of output: 1737


Make Pi session scanning asynchronous before using the result.

PiSessionStore.load(filePath) reads the file synchronously and parses JSONL synchronously. The session synchronizer calls it inside a file-scanned loop, so large or numerous transcript files block event-loop work while scanning. If keeping this synchronizer, remove it from the HTTP/WS flow or convert PiSessionStore.load / this loop to an async path that does not call synchronous file read and parse inline.

🤖 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/pi/pi-session-synchronizer.provider.ts` around
lines 33 - 43, The Pi session synchronization path currently performs
synchronous file reads and JSONL parsing during scanning. Update
PiSessionStore.load and the loop in the session synchronizer to use an
asynchronous, non-blocking load/parse path, await the result before incrementing
processed, and preserve per-file error handling; alternatively remove this
synchronizer from HTTP/WS execution if that is the established design.

Comment on lines +42 to +54
async withProbe(fn) {
const client = new PiRpcClient();
await client.start();
try {
return await fn(client as unknown as PiModelsProbe);
} finally {
try {
await client.close(MODELS_PROBE_GRACE_MS);
} catch {
// ignore close failures during probe teardown
}
}
},

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.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify PiRpcClient's public surface against PiModelsProbe (getAvailableModels/getState/start/close).
fd -t f 'pi-rpc-client.provider.ts' server | xargs -r ast-grep outline --items all
fd -t f 'pi-rpc-client.provider.ts' server | xargs -r rg -n -C3 'async (start|close|getAvailableModels|getState)\s*\('

Repository: siteboon/claudecodeui

Length of output: 1499


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Files:"
fd -t f 'pi\.provider\.ts$|pi-rpc-client\.provider\.ts$|pi-.*probe|models-rpc|ModelsRpc' server

echo
echo "pi.provider.ts relevant section:"
sed -n '1,120p' server/modules/providers/list/pi/pi.provider.ts

echo
echo "pi-rpc-client.provider.ts relevant section:"
sed -n '1,230p' server/modules/providers/list/pi/pi-rpc-client.provider.ts

echo
echo "Search for PiModelsProbe/interface/adapter:"
rg -n "PiModelsProbe|PiModelsRpc|getAvailableModels|getState|RpcClientDeps|withProbe" server

Repository: siteboon/claudecodeui

Length of output: 15499


Close the PiRpcClient when start() rejects, and stop suppressing its probe surface.

Move client.start() inside the probe lifecycle or initialize it in the same try; otherwise a rejected start never reaches finally, leaving the spawned Pi subprocess alive. Also pass client directly to fn; client already exposes getAvailableModels() and getState() via PiRpcClient, so as unknown as PiModelsProbe hides the type check.

🤖 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/pi/pi.provider.ts` around lines 42 - 54, Update
withProbe so client.start() executes inside the try/finally lifecycle, ensuring
client.close(MODELS_PROBE_GRACE_MS) runs when startup rejects. Pass client
directly to fn without the unknown-to-PiModelsProbe cast, preserving type
checking through PiRpcClient’s exposed probe methods.

Comment on lines +25 to +26
if (selectedAgent === 'pi') {
return ['account', 'permissions', 'skills'];

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.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Do not expose an empty Pi Permissions tab.

When selectedAgent === 'pi', this list includes permissions. However, AgentCategoryContentSection.tsx renders permission content only for claude, cursor, and codex; it has no Pi branch. Selecting Pi and Permissions therefore renders an empty content area. Add the Pi permissions renderer before exposing this tab, or remove permissions until the feature exists. The PR objective includes Pi permission modes.

🤖 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/components/settings/view/tabs/agents-settings/AgentsSettingsTab.tsx`
around lines 25 - 26, The Pi tab list exposes permissions without corresponding
content, resulting in an empty tab. Update the selectedAgent handling in
AgentsSettingsTab so Pi permissions is only exposed once
AgentCategoryContentSection supports a Pi renderer; preferably add the Pi
permissions content path there, otherwise remove permissions from Pi’s returned
categories until implemented.

@blackmammoth

Copy link
Copy Markdown
Member

hey @keenJoe, make sure to do the following:

  1. use only English for the content in the PR
  2. don't add docs and openspecs as it's not needed
  3. remove every non essential changes.

After doing this, resubmit the PR

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.

2 participants