Skip to content

feat(cli): add local control plane CLI - #2088

Merged
yyhhyyyyyy merged 52 commits into
devfrom
feat/local-control-plane-cli-v1
Aug 6, 2026
Merged

feat(cli): add local control plane CLI#2088
yyhhyyyyyy merged 52 commits into
devfrom
feat/local-control-plane-cli-v1

Conversation

@yyhhyyyyyy

@yyhhyyyyyy yyhhyyyyyy commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Ship a bundled deepchat CLI backed by the DeepChat main process over HTTP on UDS/named pipes.
  • Start the local control plane automatically with DeepChat, reconcile the launcher without a UI toggle, and stop serving when the desktop app exits.
  • Add model invocation, image/video/audio generation, transcription, OCR, artifacts, detached agent runs, provider/model administration, settings, Skills, and MCP operations.
  • Add versioned CLI surface contracts with caller, scope, and effect policies.
  • Add renderer-owned approval for sensitive CLI mutations using the shared approval state machine.
  • Add bounded request bodies, scoped agent tokens, artifact retention limits, audit logging, path validation, and redacted public errors.
  • Package the CLI launchers and bundled Node runtime for macOS, Windows, and Linux.
  • Document CLI usage and the local control plane architecture.

CLI UX

deepchat help
deepchat <domain> <verb> [options]
deepchat <domain> <verb> --help

Approval UI

Before:

DeepChat
└── No approval surface for external CLI mutations

After:

DeepChat
└── CLI approval dialog
    ├── Operation and argument preview
    └── Deny / Allow

The CLI can only wait for an opaque request ID. Approval resolution remains exclusive to the renderer IPC boundary.

Summary by CodeRabbit

  • New Features

    • Added a bundled DeepChat CLI for inference, media generation, transcription, OCR, artifacts, settings, Skills, MCP, provider/model administration, Agent runs, and diagnostics.
    • Added secure local communication, authentication, streaming, uploads, downloads, approvals, scoped Agent access, and event recovery.
    • Added cross-platform launcher installation and packaging support.
    • Added renderer approval dialogs and bundled read-only CLI Skill support.
  • Documentation

    • Added CLI usage and local control plane architecture guides.
  • Tests

    • Expanded coverage for CLI operations, security, approvals, packaging, artifacts, events, and integrations.

@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

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

The pull request implements Local Control Plane V1 and a bundled deepchat CLI. It adds typed contracts, authenticated local transport, main-process services, approvals, Agent controls, artifacts, events, packaging, documentation, and validation coverage.

Changes

Local Control Plane and Bundled CLI

Layer / File(s) Summary
Architecture and shared contracts
docs/architecture/local-control-plane/*, docs/guides/cli.md, src/shared/contracts/*, src/shared/types/*
Defines the local-control protocol, route and event contracts, CLI command catalog, caller types, capability metadata, and public model, media, OCR, artifact, run, provider, Skill, MCP, settings, and speech contracts.
CLI client and packaging
src/cli/*, scripts/build-cli.mjs, package.json, electron-builder.yml, tsconfig.node.json
Adds argument parsing, descriptor discovery, authenticated RPC, upload and stream transport, bounded input, output formatting, artifact downloads, exit codes, bundled launchers, and packaged CLI resources.
Main-process control plane
src/main/cli/*, src/main/app/*, src/main/events/*
Adds the local-control server, descriptor lifecycle, artifact spool, compute and input services, administration routes, detached runs, typed events, audit logging, policy enforcement, Agent tokens, and application startup and shutdown wiring.
Approval and renderer integration
src/main/approval/*, src/main/tool/permission/*, src/renderer/*
Adds the shared approval broker, renderer-bound CLI approval flow, approval UI, tool-permission integration, shell-control detection, and scoped Agent command environments.
Skills and supporting runtime changes
src/main/skill/*, src/main/provider/*, src/main/routes/*, src/main/desktop/*, test/*, resources/*
Adds bounded Skill archive handling, read-only bundled deepchat-cli Skills, standalone speech generation, validated renderer caller contexts, reset coordination, registry updates, packaging checks, and focused integration tests.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: adding a bundled CLI backed by a local control plane.
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 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/local-control-plane-cli-v1

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: 14

🧹 Nitpick comments (16)
test/main/cli/runService.test.ts (1)

198-207: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert the specific rejection for an oversized prompt.

rejects.toBeDefined() passes for any rejection, including a harness failure such as a missing route. The prompt limit is an input-bounding control, so pin the error code as the neighbouring tests do.

♻️ Proposed change
     await expect(
       invokeRoute(service, sessionsRunDetachedRoute.name, {
         prompt: 'x'.repeat(RUN_PROMPT_MAX_CHARACTERS + 1)
       })
-    ).rejects.toBeDefined()
+    ).rejects.toMatchObject({ code: 'invalid_request' })
     expect(lifecycle.createDetachedSession).not.toHaveBeenCalled()

Adjust the expected code if the route reports a different one for input validation.

🤖 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 `@test/main/cli/runService.test.ts` around lines 198 - 207, Update the
oversized-prompt test around invokeRoute to assert the specific input-validation
error code, matching the neighboring tests and the code reported by
sessionsRunDetachedRoute, instead of only checking that a rejection exists. Keep
the assertion that createDetachedSession is not called.
test/main/routes/routeRegistry.test.ts (1)

27-55: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add the accept path for requireRendererCaller.

The tests only assert rejection for non-renderer callers. A regression that rejects a valid renderer caller would break every renderer-only route and stay undetected here.

♻️ Proposed addition
   it.each<RouteContext>([

Add before the it.each block:

it('returns the renderer caller at renderer boundaries', () => {
  const context = createRendererRouteContext(42, 7)

  expect(requireRendererCaller(context)).toEqual(context.caller)
})
🤖 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 `@test/main/routes/routeRegistry.test.ts` around lines 27 - 55, Add a
positive-path test alongside the existing renderer-boundary rejection cases,
using createRendererRouteContext to construct a valid renderer context and
asserting requireRendererCaller returns context.caller unchanged.
test/main/contracts/localControl.test.ts (1)

30-68: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Split the multi-violation negative cases into one case per constraint.

The descriptor object at Lines 32-41 violates five independent rules at once: protocolVersion pinning, NUL rejection in the endpoint path, the pid lower bound, the token length, and the unknown-key rejection. A bare .toThrow() passes if only one rule survives. The token-length and NUL-path rules can then regress silently. The request object at Lines 60-66 has the same problem for id and method.

♻️ Proposed restructuring
-  it('rejects malformed descriptors and duplicate scopes', () => {
-    expect(() =>
-      LocalControlDescriptorSchema.parse({
-        protocolVersion: 2,
-        surfaceVersion: LOCAL_CONTROL_SURFACE_VERSION,
-        appVersion: '1.2.3',
-        endpoint: { kind: 'unix', path: '/tmp/deepchat.sock\0hidden' },
-        pid: 0,
-        token: 'secret',
-        startedAt: 1_000,
-        ignored: true
-      })
-    ).toThrow()
+  const validDescriptor = {
+    protocolVersion: LOCAL_CONTROL_PROTOCOL_VERSION,
+    surfaceVersion: LOCAL_CONTROL_SURFACE_VERSION,
+    appVersion: '1.2.3',
+    endpoint: { kind: 'unix', path: '/tmp/deepchat.sock' },
+    pid: 42,
+    token: 'a'.repeat(43),
+    startedAt: 1_000
+  }
+
+  it.each([
+    ['unsupported protocol version', { protocolVersion: 2 }],
+    ['NUL in the endpoint path', { endpoint: { kind: 'unix', path: '/tmp/deepchat.sock\0hidden' } }],
+    ['non-positive pid', { pid: 0 }],
+    ['short token', { token: 'secret' }],
+    ['unknown key', { ignored: true }]
+  ])('rejects a descriptor with %s', (_label, override) => {
+    expect(LocalControlDescriptorSchema.safeParse({ ...validDescriptor, ...override }).success).toBe(
+      false
+    )
+  })
+
+  it('rejects duplicate scopes', () => {
     expect(() => LocalControlScopesSchema.parse(['models:invoke', 'models:invoke'])).toThrow(
       'Duplicate local-control scope'
     )
   })

Apply the same one-violation-per-case split to the LocalControlRpcRequestSchema negative case for id and method.

🤖 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 `@test/main/contracts/localControl.test.ts` around lines 30 - 68, Split the
combined negative tests into separate cases, each violating exactly one
constraint. In the descriptor tests around LocalControlDescriptorSchema.parse,
add independent cases for protocolVersion, endpoint NUL rejection, pid lower
bound, token length, and unknown-key rejection; in the
LocalControlRpcRequestSchema tests, separate invalid id and invalid method cases
while keeping all other fields valid.
test/main/tool/agentTools/agentBashHandler.test.ts (1)

144-152: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Remove the as never spies from private methods.

AgentBashHandler already exposes AgentCommandEnvironmentPort to tests, and AgentCommandEnvironmentPort.createEnvironment is spied on without a cast. Type the remaining test dependencies the same way instead of relying on vi.spyOn(handler as never, '...').

🤖 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 `@test/main/tool/agentTools/agentBashHandler.test.ts` around lines 144 - 152,
Update the test spies for AgentBashHandler’s private methods, including
prepareCommand and runShellProcess, to avoid as never casts. Use the existing
AgentCommandEnvironmentPort-based typing pattern so these dependencies remain
type-safe and consistent with the createEnvironment spy.

Source: Coding guidelines

test/main/cli/args.test.ts (1)

122-126: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Reuse DEFAULT_COMPUTE_TIMEOUT_MS instead of the literal.

This file already imports DEFAULT_COMPUTE_TIMEOUT_MS and uses it at line 184 and line 242. Lines 124, 406, and 647 assert the same value as the literal 1_800_000. Use the imported constant at those three sites so a change to the default updates every expectation together.

♻️ Proposed change (apply at lines 124, 406, and 647)
       operation: 'stream',
       readStdin: true,
-      timeoutMs: 1_800_000,
+      timeoutMs: DEFAULT_COMPUTE_TIMEOUT_MS,
       params: {
🤖 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 `@test/main/cli/args.test.ts` around lines 122 - 126, Replace the literal
1_800_000 with the imported DEFAULT_COMPUTE_TIMEOUT_MS in the expectations at
the stream operation block and the corresponding sites around lines 406 and 647,
preserving the existing assertions.
test/main/cli/launcherService.test.ts (1)

225-254: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

POSIX-only filesystem assertions run without a platform guard. Both suites assert real host filesystem semantics — directory and file symbolic links, and POSIX permission bits — while the PR packages and ships the CLI for Windows. On Windows, fs.promises.symlink typically fails with EPERM for unprivileged accounts, and mode & 0o111 is always 0. Each case then fails for a host reason rather than a product reason.

  • test/main/cli/launcherService.test.ts#L225-L254: gate this case and the related permission-bit and chmod cases (lines 94, 270, 306, 321-332) on a non-win32 host.
  • test/main/cli/mediaOutput.test.ts#L89-L105: skip this case on win32, or pass the 'junction' type for the directory link at line 93.
🤖 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 `@test/main/cli/launcherService.test.ts` around lines 225 - 254, Gate the
POSIX-specific assertions in launcherService.test and mediaOutput.test so they
do not run on win32 hosts: update the launcherService test block around the
fails-closed scenario, and the related permission-bit/chmod cases in the same
file, to only execute on non-win32, and either skip the mediaOutput link
assertion on win32 or switch its directory symlink setup to use the junction
form. Keep the existing product assertions and test flow unchanged for
non-Windows platforms.
src/main/cli/surface.ts (1)

256-267: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider stripping control characters on the reviewable branch.

When includeReviewableValues is true, description and icon pass through unchanged. agentMcpAddInputAllowed rejects directional controls, so endpoint spoofing in the approval dialog is already blocked. It does not reject C0/C1 control characters, which the non-reviewable branch removes through sanitizePublicText. The result is display noise in the approval dialog rather than a bypass.

A control-character strip that preserves full length keeps the value fully reviewable and removes the difference between the two branches.

🤖 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/main/cli/surface.ts` around lines 256 - 267, The reviewable branch should
also remove C0/C1 control characters while preserving the full remaining value
for display. Update the includeReviewableValues handling in the description and
icon projection logic, reusing the existing sanitization behavior or helper
without truncating reviewable text, while leaving directional-control rejection
and non-reviewable truncation behavior unchanged.
src/shared/contracts/routes/cli.routes.ts (1)

19-20: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Reuse the shared local-control schemas instead of redefining them. Both sites redeclare a contract that src/shared/contracts/localControl.ts already exports, so the principal and scope contracts now have more than one definition. Import the existing export at each site.

  • src/shared/contracts/routes/cli.routes.ts#L19-L20: replace z.array(LocalControlScopeSchema).min(1) with LocalControlScopesSchema.min(1), which already bounds the array and rejects duplicates, and add a duplicate check to callers.
  • src/shared/contracts/events/approvals.events.ts#L13-L13: replace the inline z.enum(['human', 'agent']) with LocalControlPrincipalSchema, imported from the ../localControl module this file already imports from.
🤖 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/shared/contracts/routes/cli.routes.ts` around lines 19 - 20, Reuse the
shared local-control schemas at both sites: in
src/shared/contracts/routes/cli.routes.ts lines 19-20, use
LocalControlScopesSchema.min(1) for scopes and add duplicate rejection to
callers while using the shared principal schema; in
src/shared/contracts/events/approvals.events.ts line 13, replace the inline
principal enum with LocalControlPrincipalSchema from the existing
../localControl import.
src/cli/format.ts (2)

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

Add an exhaustive default branch to formatHumanResult.

The switch has no default branch, and the function declares a string return type. The code compiles today only because the cases cover every member of CliRpcContract. If a new contract is added to the union and this switch is not updated, the function returns undefined at runtime and the CLI prints undefined. A never guard converts that into a compile-time error.

♻️ Proposed exhaustiveness guard
       ].join('\n')
     }
+    default: {
+      const unhandled: never = contract
+      throw new Error(
+        `Unsupported CLI contract: ${(unhandled as { name: string }).name}`
+      )
+    }
   }
 }

Also applies to: 279-280

🤖 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/cli/format.ts` at line 26, Update the switch in formatHumanResult to
include an exhaustive default branch that passes the unmatched contract value to
a never guard, ensuring newly added CliRpcContract members produce a
compile-time error instead of allowing an undefined return. Preserve the
existing formatting cases and string return behavior.

199-208: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Reuse formatMcpRuntime in the mcp.listPublic branch.

Line 204 repeats the runtime formatting logic that formatMcpRuntime already implements, with a different label (runtime-unknown instead of unknown). Reuse the helper to keep one runtime label per state.

♻️ Proposed refactor
-            `${server.name}  ${server.type}  ${server.enabled ? 'enabled' : 'disabled'}  ${server.running === null ? 'runtime-unknown' : server.running ? 'running' : 'stopped'}  ${server.managedBy}${server.metadataTruncated ? '  metadata-truncated' : ''}  ${server.description}`
+            `${server.name}  ${server.type}  ${server.enabled ? 'enabled' : 'disabled'}  ${formatMcpRuntime(server.running)}  ${server.managedBy}${server.metadataTruncated ? '  metadata-truncated' : ''}  ${server.description}`
🤖 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/cli/format.ts` around lines 199 - 208, Update the mcp.listPublic branch
to call the existing formatMcpRuntime helper for each server’s running state
instead of duplicating the ternary formatting. Preserve the surrounding server
fields and truncation message while using the helper’s canonical runtime labels.
src/main/cli/artifactRoutes.ts (1)

36-45: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Document why artifacts.read returns only metadata.

The artifacts.read handler body is identical to the artifacts.describe handler body. Both call artifactSpool.describe. The byte payload arrives over the separate GET artifact path that src/main/cli/server.ts dispatches to handleArtifactDownload, and src/cli/format.ts line 75 reports the saved size from that download. The design is correct, but the two identical bodies invite a future reader to treat one of them as a copy-paste defect.

Add a short comment that states artifacts.read is the metadata pre-flight for the streamed download.

🤖 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/main/cli/artifactRoutes.ts` around lines 36 - 45, Add a concise comment
immediately above the artifacts.read handler in the route definitions explaining
that it returns metadata as the pre-flight for the separately streamed artifact
download. Leave the existing parsing, caller validation, and
artifactSpool.describe behavior unchanged.
src/cli/index.ts (1)

14-19: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Rename ignoreBrokenPipe to match its behavior.

The function does not ignore the error. It terminates the process: exit code 0 for EPIPE, exit code 8 for every other stream error. A name such as exitOnStreamError describes the actual behavior.

🤖 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/cli/index.ts` around lines 14 - 19, Rename ignoreBrokenPipe in the CLI
stream error handler to reflect that it exits the process rather than ignoring
errors, and update any call sites in the same module to use the new name. Keep
the existing NodeJS.WriteStream error handling behavior in place, including
process.exit(0) for EPIPE and process.exit(8) for all other stream errors, and
prefer a name like exitOnStreamError that matches the symbol’s actual effect.
src/main/approval/routes.ts (1)

4-9: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Import RendererRouteCaller instead of deriving it with ReturnType.

src/routes/routeRegistry exports RendererRouteCaller as a named type, and src/main/app/routes.ts line 26 imports it directly. Use the same named type here so one concept has one name.

♻️ Proposed refactor
-import { createRouteMap, requireRendererCaller } from '`@/routes/routeRegistry`'
+import {
+  createRouteMap,
+  requireRendererCaller,
+  type DeepchatRouteMap,
+  type RendererRouteCaller
+} from '`@/routes/routeRegistry`'
 
 export type ApprovalRoutesDependencies = Readonly<{
   resolve(
     input: { requestId: string; decision: 'approved' | 'denied' },
-    caller: ReturnType<typeof requireRendererCaller>
+    caller: RendererRouteCaller
   ): boolean
 }>
 
-export function createApprovalRoutes(dependencies: ApprovalRoutesDependencies) {
+export function createApprovalRoutes(dependencies: ApprovalRoutesDependencies): DeepchatRouteMap {
🤖 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/main/approval/routes.ts` around lines 4 - 9, Update
ApprovalRoutesDependencies.resolve to use the named RendererRouteCaller type
exported by routeRegistry instead of ReturnType<typeof requireRendererCaller>,
and add the corresponding type import while preserving the existing resolve
signature.
src/main/cli/audioTranscriptionService.ts (1)

179-189: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Parse transcription results with the shared route output schema.

Both audio.transcribeArtifact and audio.transcribeUpload return through transcribeFile, but the result is parsed through audioTranscribeUploadRoute.output. Use AudioTranscriptionOutputSchema directly to keep the artifact response coupled only to the shared output schema.

♻️ Proposed refactor
 import {
   AUDIO_TRANSCRIPTION_MAX_INPUT_BYTES,
   AUDIO_TRANSCRIPTION_MAX_TEXT_CHARACTERS,
   AudioInputMimeTypeSchema,
+  AudioTranscriptionOutputSchema,
   audioTranscribeArtifactRoute,
   audioTranscribeUploadRoute,
-      return audioTranscribeUploadRoute.output.parse({
+      return AudioTranscriptionOutputSchema.parse({
🤖 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/main/cli/audioTranscriptionService.ts` around lines 179 - 189, Update the
return parsing in transcribeFile to use the shared
AudioTranscriptionOutputSchema directly instead of
audioTranscribeUploadRoute.output, while preserving the existing transcription
result fields and truncation behavior for both artifact and upload callers.
src/main/cli/ocrService.ts (1)

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

Validate extraction output against the shared schema, not the upload route.

toImageOutput and toDocumentOutput serve both the upload path and the artifact path, but both parse through ocrExtractUploadRoute.output. The behavior is identical today because both routes reuse OcrExtractionOutputSchema. Reference OcrExtractionOutputSchema directly so the artifact path stays correct if the two route outputs diverge later.

♻️ Proposed refactor
-import {
-  OCR_EXTRACTION_MAX_INPUT_BYTES,
-  OcrInputMimeTypeSchema,
+import {
+  OCR_EXTRACTION_MAX_INPUT_BYTES,
+  OcrExtractionOutputSchema,
+  OcrInputMimeTypeSchema,
-    return ocrExtractUploadRoute.output.parse({
+    return OcrExtractionOutputSchema.parse({
       kind: 'image',
-    return ocrExtractUploadRoute.output.parse({
+    return OcrExtractionOutputSchema.parse({
       kind: 'document',

Also applies to: 297-297

🤖 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/main/cli/ocrService.ts` at line 275, Update the output parsing in
toImageOutput and toDocumentOutput to validate directly with the shared
OcrExtractionOutputSchema instead of ocrExtractUploadRoute.output. Keep the
existing parsed output behavior unchanged for both upload and artifact paths.
src/main/app/composition.ts (1)

1075-1079: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use logger instead of console.warn for the CLI Skill activity failure.

Every other CLI dependency in this composition receives logger. This callback writes to console.warn, so the failure does not reach the application log sink.

♻️ Proposed change
     recordSettingsActivity: (input) => {
       void settingsDatabase.recordSettingsActivity(input).catch((error) => {
-        console.warn('[SettingsActivity] Failed to record CLI Skill activity:', error)
+        logger.warn('[SettingsActivity] Failed to record CLI Skill activity:', error)
       })
     },
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/app/composition.ts` around lines 1075 - 1079, Update the
recordSettingsActivity callback to use the existing logger for recording CLI
Skill activity failures instead of console.warn, preserving the current error
message and asynchronous catch behavior so the failure reaches the application
log sink.
🤖 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 `@src/cli/args.ts`:
- Around line 458-466: Update the --timeout handling in the argument parser to
reject the next token when it is flag-shaped, matching the guard used by
readOptionValue, and report “Missing value for --timeout” without consuming that
flag. Preserve normal timeout parsing for non-flag values.

In `@src/cli/artifacts.ts`:
- Around line 141-143: Update the MIME-type comparison in the artifact
validation block to parse contentType and compare only its media type against
input.metadata.mimeType, preserving the existing normalization and
protocolFailure behavior. Reuse the project’s established Content-Type parser if
available, and ensure parameter-bearing values such as charset do not trigger a
mismatch.

In `@src/main/cli/artifactSpool.ts`:
- Around line 550-571: Hoist the stored-artifact usage calculation out of the
per-chunk write loop, using a single snapshot before the loop in the surrounding
write method. Update reserveBytes to validate against that snapshot plus the
reservation delta accumulated during the current write, rather than rebuilding
artifacts and rescanning quotas for every chunk. Keep reserveBytes responsible
for recording reservations and preserve all existing owner, request, connection,
and total-limit checks.

In `@src/main/cli/computeService.ts`:
- Around line 275-296: Update the queued emission handling around
executeWithRateLimit so repeated onQueued callbacks chain each emitEvent call
onto the previous queuedEmission promise instead of overwriting it. Preserve
queuedEmissionError propagation and ensure the final await does not complete
until all queued rate-limit events have finished writing.

In `@src/main/cli/launcherService.ts`:
- Around line 305-310: Update the Windows executable path logic in the launcher
service’s platform branch to use a program-owned directory under localAppData,
such as a MyApp/bin path, instead of Microsoft/WindowsApps; ensure installation
and any PATH update use this same directory while preserving the deepchat.cmd
filename.

In `@src/main/cli/mutationGuard.ts`:
- Around line 84-111: Update the scopeKey construction in the mutation approval
flow around approvalRequestId and approvals.create to use only
cli:${input.connectionId}, removing executionId from the scope. Preserve
executionId as the bindingKey so each mutation remains uniquely identified, and
leave resolve’s use of the recorded scopeKey unchanged.

In `@src/main/cli/ocrService.ts`:
- Around line 96-124: Wrap the ocrClearCacheRoute switch case body in braces so
its local declarations, especially const status, are scoped to that clause and
noSwitchDeclarations is satisfied. Keep the existing permission, cache-clearing,
error handling, status validation, and return behavior unchanged.

In `@src/main/cli/runService.ts`:
- Around line 127-154: The projectMessagePage budget loop must always include
the newest projected message, even when its serialized size exceeds
RUN_SNAPSHOT_MESSAGE_BUDGET_BYTES. Admit the newest message before enforcing the
budget, then apply the existing size cap to older messages so pagination retains
the correct cursor and never returns an empty messages array for a non-empty
page.

In `@src/shared/contracts/routes/runs.routes.ts`:
- Around line 11-14: Update PublicRunMessageSchema.text in the shared runs route
contracts to enforce RUN_MESSAGE_MAX_TEXT_BYTES based on UTF-8 byte length,
while preserving its existing validation behavior. Add a regression test
covering multibyte text whose character count is within the limit but whose
UTF-8 byte length exceeds it, ensuring runs.get rejects or excludes oversized
public messages.

In `@src/shared/utils/filesystem.ts`:
- Around line 12-15: Update isHardlinkUnavailableError in filesystem.ts to guard
the unknown error before reading .code, so null or undefined inputs return false
instead of throwing a TypeError. Keep the existing HARDLINK_UNAVAILABLE_CODES
check intact, and use a nullish-safe narrowing path around the current
NodeJS.ErrnoException code access so callers like skillService can still fall
back normally.

In `@test/main/cli/packagedSmoke.test.ts`:
- Around line 43-56: Set an explicit per-test timeout on the packaged CLI smoke
test declared by the `it` call, using a value large enough to cover the full
`buildCli` operation and all sequential 5-second subprocess waits. Keep the test
body and existing global timeout behavior unchanged.

In `@test/main/provider/providerRuntime.test.ts`:
- Around line 605-625: Update the generateSpeechStandalone test at
test/main/provider/providerRuntime.test.ts:605-625 so the streamed
image_data.data and expected audio.data use bare base64 rather than a data: URI.
Update the computeService mock at test/main/cli/computeService.test.ts:142-146
to use the same bare-base64 shape; no other behavior needs changing.

In `@test/main/scripts/buildCli.test.ts`:
- Line 62: Guard the executable-bit assertion in the build CLI test around the
existing stat check so it runs only when process.platform is not win32; preserve
the current 0o111 validation on supported POSIX platforms.
- Around line 47-51: Set an explicit 15,000 ms timeout on the standalone CLI
build test in the it block beginning “builds a standalone Node entry and
explicit bundled-runtime launchers.” Keep the test body and global Vitest
timeout unchanged.

---

Nitpick comments:
In `@src/cli/format.ts`:
- Line 26: Update the switch in formatHumanResult to include an exhaustive
default branch that passes the unmatched contract value to a never guard,
ensuring newly added CliRpcContract members produce a compile-time error instead
of allowing an undefined return. Preserve the existing formatting cases and
string return behavior.
- Around line 199-208: Update the mcp.listPublic branch to call the existing
formatMcpRuntime helper for each server’s running state instead of duplicating
the ternary formatting. Preserve the surrounding server fields and truncation
message while using the helper’s canonical runtime labels.

In `@src/cli/index.ts`:
- Around line 14-19: Rename ignoreBrokenPipe in the CLI stream error handler to
reflect that it exits the process rather than ignoring errors, and update any
call sites in the same module to use the new name. Keep the existing
NodeJS.WriteStream error handling behavior in place, including process.exit(0)
for EPIPE and process.exit(8) for all other stream errors, and prefer a name
like exitOnStreamError that matches the symbol’s actual effect.

In `@src/main/app/composition.ts`:
- Around line 1075-1079: Update the recordSettingsActivity callback to use the
existing logger for recording CLI Skill activity failures instead of
console.warn, preserving the current error message and asynchronous catch
behavior so the failure reaches the application log sink.

In `@src/main/approval/routes.ts`:
- Around line 4-9: Update ApprovalRoutesDependencies.resolve to use the named
RendererRouteCaller type exported by routeRegistry instead of ReturnType<typeof
requireRendererCaller>, and add the corresponding type import while preserving
the existing resolve signature.

In `@src/main/cli/artifactRoutes.ts`:
- Around line 36-45: Add a concise comment immediately above the artifacts.read
handler in the route definitions explaining that it returns metadata as the
pre-flight for the separately streamed artifact download. Leave the existing
parsing, caller validation, and artifactSpool.describe behavior unchanged.

In `@src/main/cli/audioTranscriptionService.ts`:
- Around line 179-189: Update the return parsing in transcribeFile to use the
shared AudioTranscriptionOutputSchema directly instead of
audioTranscribeUploadRoute.output, while preserving the existing transcription
result fields and truncation behavior for both artifact and upload callers.

In `@src/main/cli/ocrService.ts`:
- Line 275: Update the output parsing in toImageOutput and toDocumentOutput to
validate directly with the shared OcrExtractionOutputSchema instead of
ocrExtractUploadRoute.output. Keep the existing parsed output behavior unchanged
for both upload and artifact paths.

In `@src/main/cli/surface.ts`:
- Around line 256-267: The reviewable branch should also remove C0/C1 control
characters while preserving the full remaining value for display. Update the
includeReviewableValues handling in the description and icon projection logic,
reusing the existing sanitization behavior or helper without truncating
reviewable text, while leaving directional-control rejection and non-reviewable
truncation behavior unchanged.

In `@src/shared/contracts/routes/cli.routes.ts`:
- Around line 19-20: Reuse the shared local-control schemas at both sites: in
src/shared/contracts/routes/cli.routes.ts lines 19-20, use
LocalControlScopesSchema.min(1) for scopes and add duplicate rejection to
callers while using the shared principal schema; in
src/shared/contracts/events/approvals.events.ts line 13, replace the inline
principal enum with LocalControlPrincipalSchema from the existing
../localControl import.

In `@test/main/cli/args.test.ts`:
- Around line 122-126: Replace the literal 1_800_000 with the imported
DEFAULT_COMPUTE_TIMEOUT_MS in the expectations at the stream operation block and
the corresponding sites around lines 406 and 647, preserving the existing
assertions.

In `@test/main/cli/launcherService.test.ts`:
- Around line 225-254: Gate the POSIX-specific assertions in
launcherService.test and mediaOutput.test so they do not run on win32 hosts:
update the launcherService test block around the fails-closed scenario, and the
related permission-bit/chmod cases in the same file, to only execute on
non-win32, and either skip the mediaOutput link assertion on win32 or switch its
directory symlink setup to use the junction form. Keep the existing product
assertions and test flow unchanged for non-Windows platforms.

In `@test/main/cli/runService.test.ts`:
- Around line 198-207: Update the oversized-prompt test around invokeRoute to
assert the specific input-validation error code, matching the neighboring tests
and the code reported by sessionsRunDetachedRoute, instead of only checking that
a rejection exists. Keep the assertion that createDetachedSession is not called.

In `@test/main/contracts/localControl.test.ts`:
- Around line 30-68: Split the combined negative tests into separate cases, each
violating exactly one constraint. In the descriptor tests around
LocalControlDescriptorSchema.parse, add independent cases for protocolVersion,
endpoint NUL rejection, pid lower bound, token length, and unknown-key
rejection; in the LocalControlRpcRequestSchema tests, separate invalid id and
invalid method cases while keeping all other fields valid.

In `@test/main/routes/routeRegistry.test.ts`:
- Around line 27-55: Add a positive-path test alongside the existing
renderer-boundary rejection cases, using createRendererRouteContext to construct
a valid renderer context and asserting requireRendererCaller returns
context.caller unchanged.

In `@test/main/tool/agentTools/agentBashHandler.test.ts`:
- Around line 144-152: Update the test spies for AgentBashHandler’s private
methods, including prepareCommand and runShellProcess, to avoid as never casts.
Use the existing AgentCommandEnvironmentPort-based typing pattern so these
dependencies remain type-safe and consistent with the createEnvironment spy.
🪄 Autofix

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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 709b8cef-3f87-447f-ae0b-013ee161b0db

📥 Commits

Reviewing files that changed from the base of the PR and between 6567fb6 and 45c2af5.

📒 Files selected for processing (157)
  • docs/README.md
  • docs/architecture/local-control-plane/plan.md
  • docs/architecture/local-control-plane/spec.md
  • docs/architecture/local-control-plane/tasks.md
  • docs/guides/cli.md
  • electron-builder.yml
  • package.json
  • resources/acp-registry/registry.json
  • resources/skills/deepchat-cli/SKILL.md
  • scripts/build-cli.mjs
  • src/cli/args.ts
  • src/cli/artifacts.ts
  • src/cli/discovery.ts
  • src/cli/errors.ts
  • src/cli/format.ts
  • src/cli/globals.d.ts
  • src/cli/index.ts
  • src/cli/run.ts
  • src/cli/stdin.ts
  • src/cli/transport.ts
  • src/main/app/applicationDataReset.ts
  • src/main/app/composition.ts
  • src/main/app/routes.ts
  • src/main/app/settingsRoutes.ts
  • src/main/approval/approvalBroker.ts
  • src/main/approval/index.ts
  • src/main/approval/routes.ts
  • src/main/cli/agentCommandAccess.ts
  • src/main/cli/agentTokenAuthority.ts
  • src/main/cli/artifactRoutes.ts
  • src/main/cli/artifactSpool.ts
  • src/main/cli/audioTranscriptionService.ts
  • src/main/cli/auditLog.ts
  • src/main/cli/body.ts
  • src/main/cli/computeService.ts
  • src/main/cli/descriptor.ts
  • src/main/cli/errors.ts
  • src/main/cli/index.ts
  • src/main/cli/launcherService.ts
  • src/main/cli/mcpAdminRoutes.ts
  • src/main/cli/mediaOutput.ts
  • src/main/cli/mutationGuard.ts
  • src/main/cli/ocrService.ts
  • src/main/cli/policy.ts
  • src/main/cli/providerModelAdminRoutes.ts
  • src/main/cli/publicText.ts
  • src/main/cli/routes.ts
  • src/main/cli/runService.ts
  • src/main/cli/server.ts
  • src/main/cli/skillService.ts
  • src/main/cli/surface.ts
  • src/main/desktop/routes.ts
  • src/main/desktop/sessionBinding.ts
  • src/main/events/sessionEventRouter.ts
  • src/main/events/typedEventHub.ts
  • src/main/mcp/routes.ts
  • src/main/notifications/electronWindowNotificationTargets.ts
  • src/main/notifications/routes.ts
  • src/main/ocr/ocrRuntimeService.ts
  • src/main/ocr/routes.ts
  • src/main/provider/index.ts
  • src/main/provider/routes.ts
  • src/main/routes/index.ts
  • src/main/routes/routeRegistry.ts
  • src/main/session/data/tables/deepchatSessionMetadata.ts
  • src/main/session/routes.ts
  • src/main/session/sessionService.ts
  • src/main/skill/archive.ts
  • src/main/skill/archiveDownload.ts
  • src/main/skill/index.ts
  • src/main/tool/agentTools/agentBashHandler.ts
  • src/main/tool/agentTools/agentToolManager.ts
  • src/main/tool/index.ts
  • src/main/tool/permission/commandPermissionService.ts
  • src/main/tool/permission/index.ts
  • src/main/tool/permission/toolPermissionBroker.ts
  • src/renderer/api/ApprovalClient.ts
  • src/renderer/src/apps/chat-main/ChatMainApp.vue
  • src/renderer/src/components/cli/CliApprovalDialog.vue
  • src/renderer/src/stores/cliApproval.ts
  • src/shared/contracts/cliCommands.ts
  • src/shared/contracts/common.ts
  • src/shared/contracts/contract.ts
  • src/shared/contracts/events.ts
  • src/shared/contracts/events/approvals.events.ts
  • src/shared/contracts/events/runs.events.ts
  • src/shared/contracts/json.ts
  • src/shared/contracts/localControl.ts
  • src/shared/contracts/routes.ts
  • src/shared/contracts/routes/approvals.routes.ts
  • src/shared/contracts/routes/artifacts.routes.ts
  • src/shared/contracts/routes/audio.routes.ts
  • src/shared/contracts/routes/cli.routes.ts
  • src/shared/contracts/routes/mcp.routes.ts
  • src/shared/contracts/routes/media.routes.ts
  • src/shared/contracts/routes/models.routes.ts
  • src/shared/contracts/routes/ocr.routes.ts
  • src/shared/contracts/routes/providers.routes.ts
  • src/shared/contracts/routes/runs.routes.ts
  • src/shared/contracts/routes/settings.routes.ts
  • src/shared/contracts/routes/skills.routes.ts
  • src/shared/types/agent-interface.d.ts
  • src/shared/types/provider.ts
  • src/shared/types/skill.ts
  • src/shared/utils/filesystem.ts
  • test/main/app/applicationDataReset.test.ts
  • test/main/app/routes.test.ts
  • test/main/approval/approvalBroker.test.ts
  • test/main/approval/routes.test.ts
  • test/main/cli/agentCommandAccess.test.ts
  • test/main/cli/agentTokenAuthority.test.ts
  • test/main/cli/args.test.ts
  • test/main/cli/artifactSpool.test.ts
  • test/main/cli/artifacts.test.ts
  • test/main/cli/auditLog.test.ts
  • test/main/cli/body.test.ts
  • test/main/cli/client.test.ts
  • test/main/cli/computeService.test.ts
  • test/main/cli/descriptor.test.ts
  • test/main/cli/discovery.test.ts
  • test/main/cli/errors.test.ts
  • test/main/cli/inputCapabilityServices.test.ts
  • test/main/cli/launcherService.test.ts
  • test/main/cli/mcpAdminRoutes.test.ts
  • test/main/cli/mediaOutput.test.ts
  • test/main/cli/mutationGuard.test.ts
  • test/main/cli/packagedSmoke.test.ts
  • test/main/cli/policy.test.ts
  • test/main/cli/providerModelAdminRoutes.test.ts
  • test/main/cli/runService.test.ts
  • test/main/cli/server.test.ts
  • test/main/cli/skillService.test.ts
  • test/main/cli/stdin.test.ts
  • test/main/cli/surface.test.ts
  • test/main/cli/transport.test.ts
  • test/main/contracts/localControl.test.ts
  • test/main/desktop/sessionBinding.test.ts
  • test/main/events/typedEventHub.test.ts
  • test/main/notifications/routes.test.ts
  • test/main/ocr/routes.test.ts
  • test/main/orchestration/orchestrationRoutes.test.ts
  • test/main/provider/providerRuntime.test.ts
  • test/main/provider/routes.test.ts
  • test/main/routes/dispatcher.test.ts
  • test/main/routes/routeRegistry.test.ts
  • test/main/scripts/buildCli.test.ts
  • test/main/session/data/tables/deepchatSessionMetadata.test.ts
  • test/main/session/sessionService.test.ts
  • test/main/skill/archive.test.ts
  • test/main/skill/archiveDownload.test.ts
  • test/main/skill/skillService.test.ts
  • test/main/skill/skillServiceAgentScopes.test.ts
  • test/main/tool/agentTools/agentBashHandler.test.ts
  • test/main/tool/permission/commandPermissionService.test.ts
  • test/main/tool/toolPermissionBroker.test.ts
  • test/renderer/stores/cliApproval.test.ts
  • tsconfig.node.json

Comment thread src/cli/args.ts
Comment thread src/cli/artifacts.ts
Comment thread src/main/cli/artifactSpool.ts
Comment thread src/main/cli/computeService.ts Outdated
Comment thread src/main/cli/launcherService.ts
Comment thread src/shared/utils/filesystem.ts
Comment thread test/main/cli/packagedSmoke.test.ts
Comment thread test/main/provider/providerRuntime.test.ts
Comment thread test/main/scripts/buildCli.test.ts
Comment thread test/main/scripts/buildCli.test.ts Outdated

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

🤖 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 `@test/main/shared/filesystem.test.ts`:
- Around line 5-14: Update the isHardlinkUnavailableError tests to include a
plain object with a recognized string code, such as EPERM, and assert it returns
true. Keep the existing primitive, Error-instance, and unrecognized-code cases
unchanged.
🪄 Autofix

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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: b18694b4-63f2-42e3-8de0-1f5f5a88a345

📥 Commits

Reviewing files that changed from the base of the PR and between 45c2af5 and 44bf77f.

📒 Files selected for processing (15)
  • src/cli/args.ts
  • src/main/cli/artifactSpool.ts
  • src/main/cli/computeService.ts
  • src/main/cli/mutationGuard.ts
  • src/shared/contracts/routes/runs.routes.ts
  • src/shared/utils/filesystem.ts
  • test/main/cli/args.test.ts
  • test/main/cli/artifactSpool.test.ts
  • test/main/cli/computeService.test.ts
  • test/main/cli/mutationGuard.test.ts
  • test/main/cli/packagedSmoke.test.ts
  • test/main/cli/runService.test.ts
  • test/main/scripts/buildCli.test.ts
  • test/main/shared/filesystem.test.ts
  • test/setup.renderer.ts
🚧 Files skipped from review as they are similar to previous changes (5)
  • src/shared/utils/filesystem.ts
  • test/main/cli/mutationGuard.test.ts
  • src/main/cli/mutationGuard.ts
  • src/shared/contracts/routes/runs.routes.ts
  • src/cli/args.ts

Comment thread test/main/shared/filesystem.test.ts

@zerob13 zerob13 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Review: feat(cli) add local control plane CLI

Overall: one of the most security-conscious local control plane implementations I have reviewed. The architecture decisions are sound — canonical contract reuse (no second schema catalog), the ApprovalBroker extraction with a unique, non-replayable CLI approval path, the deny-by-default CLI_SURFACE with an explicit caller model, bounded body handling with approval-before-body via Expect: 100-continue, and agent token scopes derived from the command catalog. I did not find an exploitable vulnerability in the reviewed paths (caller + scope + agentPolicy gating, TOCTOU defenses, shell-control detection). Issues below are ordered by priority.

P1 (maintainability): command metadata is spread across 5 places with no compile-time consistency

Adding a command requires touching:

  • src/shared/contracts/cliCommands.ts (definition)
  • src/cli/args.tsVALUE_DOMAIN_OPTIONS (L65) + COMMAND_DOMAIN_OPTIONS (L187)
  • the validation chain (L632-739)
  • the params-building chain (L756-959)
  • the hardcoded help text in formatCliHelp (L989)

Any missed spot is a silent drift (e.g. help text diverging from real constraints). Suggest moving per-command option definitions (name/type/constraints/params mapping/help) into CliCommandDefinition so validation and help derive from one source.

P2 (availability): audit failure fails closed and takes down ALL CLI requests

CliRequestPolicy.authorize awaits audit('allowed') for every request including read-only ones (policy.ts:227), and CliAuditLog.record rejects when the file is unwritable/closed/disk-full (auditLog.ts:42). Once the audit file is corrupt, every CLI capability (including cli.status) returns 500. Fail-closed is defensible for a security audit trail, but the blast radius includes read-only diagnostics; consider best-effort audit (log + continue) or at least not making read-only requests depend on audit success.

P3 (quota semantics): agent byte quota is consumed on failure and never refunded

ArtifactSpool consumes per-chunk during write (artifactSpool.ts:354-361), and consumeRecordBytes pins usedBytes to maxBytes on overflow (agentTokenAuthority.ts:278-280). A failed/cancelled 200MB generation permanently burns the token quota until expiry. Consider settling after success or refunding on failure.

P4 (consistency): providerModelAdminRoutes mutations lack error normalization

addProviderAtomic/updateProviderAtomic/setModelConfig are called bare (providerModelAdminRoutes.ts:141/176/252). A transient store failure surfaces as a raw 500 internal_error, non-retriable, while mcpAdminRoutes wraps the same class of failure as 503 retriable. Please normalize consistently.

P5 (semantics): agent run ownership model is split

requireOwnedRun lets an agent access sessions where conversationId === runId (runService.ts:426-429), but subscribeToRun additionally requires metadata.source === 'cli_run' (runService.ts:338), so an agent always gets 404 from events.subscribe after passing runs.get/runs.cancel. The behavior matches the spec (an agent must not watch its own run), but the denial should happen in requireOwnedRun instead of a second failure point in subscribe.

P6 (launcher): two issues that can permanently lock users in a conflict state

  • launcherService.ts:416 compares path.resolve(marker.commandPath) !== commandPath case-sensitively on win32, while isCommandDirectoryOnPath (L728) lowercases — inconsistent casing across starts flags an owned launcher as conflict/ownership-marker-invalid.
  • launcherService.ts:774 classifies a >1MB shell profile as blockState: 'modified' and refuses install ("managed block has been modified") — a large but untouched .zshrc makes installation permanently fail with a misleading error. Give it a distinct reason (e.g. profile-too-large).

P7 (small)

  • surface.ts:947artifactsReadRoute limit maxBodyBytes: 1 magic number.
  • mcpAdminRoutes.ts:240/254 — non-null assertions (updates.command!, updates.baseUrl!) after if-guards.
  • run.ts:290-299 — stdin byte limits hardcoded by contract name, duplicated from contract constants.

Consistency / dedup (medium)

  • Factory-function style (mcpAdminRoutes/providerModelAdminRoutes/artifactRoutes) vs class + createRoutes() (skillService/computeService/ocrService/audioTranscriptionService/runService) within the same PR — pick one.
  • requireCliCaller/requireHumanCliCaller duplicated 5x; error-normalization helpers 4x; removeIfPresent 3x; writeAll 2x; the launcherService atomic-file-ops cluster (~7 methods) shares patterns with artifactSpool/auditLog. A small shared cliGuards/cliFileOps module would help.

Over-engineering candidates (low)

  • TypedEventHub: 10 tunable parameters, most only exercised by tests — acceptable, but consider trimming.
  • ArtifactSpool double-track quota accounting (6 maps, ~200 lines) is correct but very hard to review; a single-track QuotaTracker would help.
  • agentTokenAuthority.createUniqueTokenId (L262) constrains the injected tokenId generator with a 16-128 regex, breaking tests that inject short ids.

Tests (non-blocking)

+11.7k lines for +31k implementation lines is a reasonable ratio and quality is high (black-box, contract-bound). No file should be deleted wholesale. Suggested trims:

  • surface.test.ts L354-589: golden table fully inlines implementation-derived data (will break on any implementation change) and overlaps the policy list at L81-99 — use a snapshot or drop the duplicate list (~-200 lines).
  • computeService.test.ts L264-379: two process-ordered timing tests — merge into one behavioral test (~-80 lines).
  • cliApproval.test.ts: 30 lines of vi.doMock boilerplate per test — extract a helper (-60 lines).
  • launcherService.test.ts: five isomorphic tests → it.each (~-60 lines).
  • args.test.ts: help assertions scattered across 5 it blocks (~-25 lines).
  • errors.test.ts L13-24: asserts a constant against a hardcoded copy of itself — drop (~-12 lines).

Confirmed good (no action)

  • ApprovalBroker extraction preserves tool semantics; CLI approvals are unique, non-deduplicated, non-replayable; approvals.resolve is renderer-only.
  • Upload flow: rejection happens before reading the body (100-continue gated behind approval); chunked bodies cannot bypass limits (body.ts).
  • Agent file boundary: no --file/--out/artifact bytes for agents, double-enforced client-side (run.ts:360-379) and server-side.
  • hasShellControlSyntax + signature hashing in commandPermissionService covers redirection, $( and backticks inside quotes.
  • Docs (spec/plan/guide/SKILL.md) match the implementation, including honest disclosure of the pre-existing shell redirection gap.

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

🤖 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 `@test/main/cli/launcherService.test.ts`:
- Around line 397-410: Extend the “matches an owned Windows marker path without
case sensitivity” test after the removeOwnedLauncher assertion to verify the
resolved Windows launcher path no longer exists. Use the fixture’s resolved
launcher-path symbol rather than the uppercased marker.commandPath, preserving
the existing ownership and removal assertions.
- Around line 338-347: Update the oversized-profile test around
fixture.service.ensureInstalled() to capture the original profile contents
before the rejection, then read the file afterward and assert the contents are
identical. Retain the existing size assertion and command-path absence check.
🪄 Autofix

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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 65b50722-20b2-4165-afa0-de9cffb92810

📥 Commits

Reviewing files that changed from the base of the PR and between 44bf77f and d7d55a8.

📒 Files selected for processing (5)
  • src/main/cli/launcherService.ts
  • src/main/cli/providerModelAdminRoutes.ts
  • test/main/cli/launcherService.test.ts
  • test/main/cli/providerModelAdminRoutes.test.ts
  • test/main/shared/filesystem.test.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • test/main/shared/filesystem.test.ts
  • test/main/cli/providerModelAdminRoutes.test.ts
  • src/main/cli/launcherService.ts

Comment thread test/main/cli/launcherService.test.ts Outdated
Comment thread test/main/cli/launcherService.test.ts

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

🤖 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 `@src/main/cli/publicText.ts`:
- Around line 25-31: Update stripC0AndC1Controls to enforce the same bounded
scan limit while iterating input, limiting both character scanning and output
allocation. Ensure callers such as mcpConfigProjection cannot process unbounded
config.description or config.icon values when includeReviewableValues is
enabled, while preserving control-character filtering for the scanned portion.
🪄 Autofix

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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 2d2eb419-bb15-484e-ab82-dbb2812023a3

📥 Commits

Reviewing files that changed from the base of the PR and between d7d55a8 and 8dcc1bd.

📒 Files selected for processing (18)
  • src/cli/format.ts
  • src/cli/index.ts
  • src/main/app/composition.ts
  • src/main/approval/routes.ts
  • src/main/cli/artifactRoutes.ts
  • src/main/cli/audioTranscriptionService.ts
  • src/main/cli/ocrService.ts
  • src/main/cli/publicText.ts
  • src/main/cli/surface.ts
  • src/shared/contracts/events/approvals.events.ts
  • src/shared/contracts/routes/cli.routes.ts
  • test/main/cli/args.test.ts
  • test/main/cli/launcherService.test.ts
  • test/main/cli/mediaOutput.test.ts
  • test/main/cli/runService.test.ts
  • test/main/cli/surface.test.ts
  • test/main/contracts/localControl.test.ts
  • test/main/routes/routeRegistry.test.ts
🚧 Files skipped from review as they are similar to previous changes (16)
  • test/main/contracts/localControl.test.ts
  • test/main/routes/routeRegistry.test.ts
  • src/main/approval/routes.ts
  • src/main/cli/artifactRoutes.ts
  • test/main/cli/surface.test.ts
  • test/main/cli/mediaOutput.test.ts
  • test/main/cli/launcherService.test.ts
  • src/cli/index.ts
  • src/shared/contracts/events/approvals.events.ts
  • src/main/cli/ocrService.ts
  • src/shared/contracts/routes/cli.routes.ts
  • src/main/cli/audioTranscriptionService.ts
  • src/cli/format.ts
  • test/main/cli/args.test.ts
  • src/main/cli/surface.ts
  • src/main/app/composition.ts

Comment thread src/main/cli/publicText.ts Outdated
@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

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

🧹 Nitpick comments (7)
src/main/tool/permission/toolPermissionBroker.ts (1)

159-167: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Log the onRequest failure before you deny the request.

The bare catch discards the error. When the renderer delivery fails, the tool call is denied with no diagnostic record. Capture the error and log it.

♻️ Proposed change
     try {
       onRequest(this.toPermissionRequest(pending))
-    } catch {
+    } catch (error) {
+      console.warn('[ToolPermission] Failed to deliver approval request', error)
       this.approvals.resolve({
🤖 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/main/tool/permission/toolPermissionBroker.ts` around lines 159 - 167,
Update the catch block around onRequest in the permission request flow to
capture the thrown error and log it before resolving the pending approval as
denied. Preserve the existing requestId, scopeKey, and denied decision behavior.
src/main/events/sessionEventRouter.ts (1)

67-71: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Rename the loop variable to avoid shadowing.

Line 68 names the callback parameter ownership. The outer array at line 45 has the same name and a different type. Rename the parameter to keep the two values distinct.

♻️ Proposed rename
-      for (const runId of new Set(cliRunOwnership.map((ownership) => ownership.runId))) {
+      for (const runId of new Set(cliRunOwnership.map((entry) => entry.runId))) {
🤖 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/main/events/sessionEventRouter.ts` around lines 67 - 71, Rename the map
callback parameter in the run-ID collection within the RUN_STREAM_EVENTS branch
to avoid shadowing the outer ownership value, and update its runId reference
accordingly; leave the loop and publishing behavior unchanged.
test/main/cli/discovery.test.ts (1)

26-30: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Temp-directory cleanup is not idempotent in two test files. Both afterEach hooks call rm(directory, { recursive: true }) without force: true. If a test already removed a directory, rm throws ENOENT and the hook fails, which reports as an unrelated suite failure. Add force: true at both sites.

  • test/main/cli/discovery.test.ts#L26-L30: change the rm call in the afterEach hook to rm(directory, { recursive: true, force: true }).
  • test/main/cli/inputCapabilityServices.test.ts#L81-L86: change the rm call in the afterEach hook to rm(directory, { recursive: true, force: true }).
🤖 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 `@test/main/cli/discovery.test.ts` around lines 26 - 30, Make both afterEach
cleanup hooks idempotent by adding force: true to the rm options in
test/main/cli/discovery.test.ts lines 26-30 and
test/main/cli/inputCapabilityServices.test.ts lines 81-86. Update the rm calls
used by each hook while preserving recursive cleanup behavior.
test/main/cli/agentTokenAuthority.test.ts (1)

53-57: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Token id generation depends on shift() call order.

createTokenId derives the id from generatedTokens.length, which createToken mutates. The ids stay unique only while issue() calls createToken before createTokenId. The other tests in this file use an independent counter, which does not depend on that order. Use the same counter here.

♻️ Proposed change
     const generatedTokens = [token('a'), token('b'), token('c')]
+    let tokenId = 0
     const authority = new AgentCliTokenAuthority({
       createToken: () => generatedTokens.shift()!,
-      createTokenId: () => `token-id-${generatedTokens.length}`.padEnd(16, '0')
+      createTokenId: () => `token-id-${String((tokenId += 1)).padStart(8, '0')}`
     })
🤖 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 `@test/main/cli/agentTokenAuthority.test.ts` around lines 53 - 57, Update the
test fixture for AgentCliTokenAuthority so createTokenId uses an independent
counter rather than generatedTokens.length, matching the counter-based setup
used by the other tests in the file. Keep generatedTokens responsible only for
returned token values and ensure each generated ID remains unique regardless of
whether createToken or createTokenId is called first.
src/shared/contracts/routes/ocr.routes.ts (1)

205-216: 🗄️ Data Integrity & Integration | 🔵 Trivial | 💤 Low value

No cross-check between artifactTermination and resourceLimit.

This file validates two other cross-field invariants: runtime readiness at Line 146 and cache-hit consistency at Line 229. The document output has a third invariant that is not enforced. artifactTermination: 'resource_limited' should imply that resourceLimit is present, and the reverse should also hold. Adding the check keeps the contract self-consistent.

♻️ Proposed change
     .strict()
-}).strict()
+})
+  .strict()
+  .superRefine((output, context) => {
+    if ((output.artifactTermination === 'resource_limited') !== (output.resourceLimit !== undefined)) {
+      context.addIssue({
+        code: 'custom',
+        message: 'OCR resource limit detail does not match its termination reason',
+        path: ['resourceLimit']
+      })
+    }
+  })

Note that z.discriminatedUnion at Line 227 requires plain object members. If the added refinement breaks the discriminated union, apply the same check inside the existing superRefine at Line 228 instead.

🤖 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/shared/contracts/routes/ocr.routes.ts` around lines 205 - 216, Update the
document output validation around the schema containing artifactTermination and
resourceLimit to enforce both directions of the invariant: artifactTermination
must be resource_limited exactly when resourceLimit is present. Prefer adding
the check in the existing superRefine near the discriminated union so its
plain-object member requirement remains valid, while preserving the
runtime-readiness and cache-hit consistency checks.
src/shared/contracts/routes/providers.routes.ts (1)

189-193: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Public connection-test route reuses a non-strict internal schema.

providersTestPublicConnectionRoute reuses providersTestConnectionRoute.input from Line 179. That schema is not .strict() and its providerId uses EntityIdSchema without .max(128). Every other public route added in this file uses .strict() and a bounded providerId. Unknown keys from a CLI caller are therefore stripped here instead of rejected.

Define a bounded strict input for the public route.

♻️ Proposed change
 export const providersTestPublicConnectionRoute = defineRouteContract({
   name: 'providers.testPublicConnection',
-  input: providersTestConnectionRoute.input,
+  input: z
+    .object({
+      providerId: EntityIdSchema.max(128),
+      modelId: z.string().min(1).max(256).optional()
+    })
+    .strict(),
   output: providersTestConnectionRoute.output
 })
🤖 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/shared/contracts/routes/providers.routes.ts` around lines 189 - 193,
Update providersTestPublicConnectionRoute to define its own strict input schema
instead of reusing providersTestConnectionRoute.input; ensure providerId uses
the bounded EntityIdSchema with a maximum length of 128, while preserving the
existing route output schema.
src/main/cli/skillService.ts (1)

79-96: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

retainUploadFile injects link but not copyFile.

The function accepts linkFile for testing, but the fallback path calls the imported copyFile directly. A test cannot exercise the fallback branch through the parameter. Consider injecting both, or neither.

♻️ Proposed change
 export async function retainUploadFile(
   uploadPath: string,
-  linkFile: typeof link = link
+  linkFile: typeof link = link,
+  copyFileFn: typeof copyFile = copyFile
 ): Promise<Readonly<{ path: string }>> {
   const retainedPath = path.join(path.dirname(uploadPath), `body-${randomUUID()}.tmp`)
   try {
     await linkFile(uploadPath, retainedPath)
   } catch (error) {
     if (!isHardlinkUnavailableError(error)) throw error
     try {
-      await copyFile(uploadPath, retainedPath, constants.COPYFILE_EXCL)
+      await copyFileFn(uploadPath, retainedPath, constants.COPYFILE_EXCL)
🤖 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/main/cli/skillService.ts` around lines 79 - 96, Update retainUploadFile
so the fallback copy operation uses an injected dependency consistently with
linkFile: either add a copyFile parameter with the imported function as its
default and call that parameter, or remove linkFile injection and use both
imported functions directly. Preserve the existing hardlink fallback and cleanup
behavior.
🤖 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 `@src/shared/contracts/routes/providers.routes.ts`:
- Around line 29-43: Update PublicProviderBaseUrlSchema’s superRefine callback
to handle URL construction failures from invalid inputs by catching new
URL(value) errors and adding a Zod validation issue instead of allowing a
TypeError to escape. Preserve support for localhost HTTP endpoints and keep the
existing protocol, credential, query, and fragment validations unchanged.

---

Nitpick comments:
In `@src/main/cli/skillService.ts`:
- Around line 79-96: Update retainUploadFile so the fallback copy operation uses
an injected dependency consistently with linkFile: either add a copyFile
parameter with the imported function as its default and call that parameter, or
remove linkFile injection and use both imported functions directly. Preserve the
existing hardlink fallback and cleanup behavior.

In `@src/main/events/sessionEventRouter.ts`:
- Around line 67-71: Rename the map callback parameter in the run-ID collection
within the RUN_STREAM_EVENTS branch to avoid shadowing the outer ownership
value, and update its runId reference accordingly; leave the loop and publishing
behavior unchanged.

In `@src/main/tool/permission/toolPermissionBroker.ts`:
- Around line 159-167: Update the catch block around onRequest in the permission
request flow to capture the thrown error and log it before resolving the pending
approval as denied. Preserve the existing requestId, scopeKey, and denied
decision behavior.

In `@src/shared/contracts/routes/ocr.routes.ts`:
- Around line 205-216: Update the document output validation around the schema
containing artifactTermination and resourceLimit to enforce both directions of
the invariant: artifactTermination must be resource_limited exactly when
resourceLimit is present. Prefer adding the check in the existing superRefine
near the discriminated union so its plain-object member requirement remains
valid, while preserving the runtime-readiness and cache-hit consistency checks.

In `@src/shared/contracts/routes/providers.routes.ts`:
- Around line 189-193: Update providersTestPublicConnectionRoute to define its
own strict input schema instead of reusing providersTestConnectionRoute.input;
ensure providerId uses the bounded EntityIdSchema with a maximum length of 128,
while preserving the existing route output schema.

In `@test/main/cli/agentTokenAuthority.test.ts`:
- Around line 53-57: Update the test fixture for AgentCliTokenAuthority so
createTokenId uses an independent counter rather than generatedTokens.length,
matching the counter-based setup used by the other tests in the file. Keep
generatedTokens responsible only for returned token values and ensure each
generated ID remains unique regardless of whether createToken or createTokenId
is called first.

In `@test/main/cli/discovery.test.ts`:
- Around line 26-30: Make both afterEach cleanup hooks idempotent by adding
force: true to the rm options in test/main/cli/discovery.test.ts lines 26-30 and
test/main/cli/inputCapabilityServices.test.ts lines 81-86. Update the rm calls
used by each hook while preserving recursive cleanup behavior.
🪄 Autofix

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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 9435e4b4-cef5-4a9d-803a-043be68fd992

📥 Commits

Reviewing files that changed from the base of the PR and between 4461a7f and 2067677.

📒 Files selected for processing (161)
  • docs/README.md
  • docs/architecture/local-control-plane/plan.md
  • docs/architecture/local-control-plane/spec.md
  • docs/architecture/local-control-plane/tasks.md
  • docs/guides/cli.md
  • electron-builder.yml
  • package.json
  • resources/acp-registry/registry.json
  • resources/model-db/providers.json
  • resources/skills/deepchat-cli/SKILL.md
  • scripts/build-cli.mjs
  • src/cli/args.ts
  • src/cli/artifacts.ts
  • src/cli/discovery.ts
  • src/cli/errors.ts
  • src/cli/format.ts
  • src/cli/globals.d.ts
  • src/cli/index.ts
  • src/cli/run.ts
  • src/cli/stdin.ts
  • src/cli/transport.ts
  • src/main/app/applicationDataReset.ts
  • src/main/app/composition.ts
  • src/main/app/routes.ts
  • src/main/app/settingsRoutes.ts
  • src/main/approval/approvalBroker.ts
  • src/main/approval/index.ts
  • src/main/approval/routes.ts
  • src/main/cli/agentCommandAccess.ts
  • src/main/cli/agentTokenAuthority.ts
  • src/main/cli/artifactRoutes.ts
  • src/main/cli/artifactSpool.ts
  • src/main/cli/audioTranscriptionService.ts
  • src/main/cli/auditLog.ts
  • src/main/cli/body.ts
  • src/main/cli/computeService.ts
  • src/main/cli/descriptor.ts
  • src/main/cli/errors.ts
  • src/main/cli/index.ts
  • src/main/cli/launcherService.ts
  • src/main/cli/mcpAdminRoutes.ts
  • src/main/cli/mediaOutput.ts
  • src/main/cli/mutationGuard.ts
  • src/main/cli/ocrService.ts
  • src/main/cli/policy.ts
  • src/main/cli/providerModelAdminRoutes.ts
  • src/main/cli/publicText.ts
  • src/main/cli/routes.ts
  • src/main/cli/runService.ts
  • src/main/cli/server.ts
  • src/main/cli/skillService.ts
  • src/main/cli/surface.ts
  • src/main/desktop/routes.ts
  • src/main/desktop/sessionBinding.ts
  • src/main/events/sessionEventRouter.ts
  • src/main/events/typedEventHub.ts
  • src/main/mcp/routes.ts
  • src/main/notifications/electronWindowNotificationTargets.ts
  • src/main/notifications/routes.ts
  • src/main/ocr/ocrRuntimeService.ts
  • src/main/ocr/routes.ts
  • src/main/provider/index.ts
  • src/main/provider/routes.ts
  • src/main/routes/index.ts
  • src/main/routes/routeRegistry.ts
  • src/main/session/data/tables/deepchatSessionMetadata.ts
  • src/main/session/routes.ts
  • src/main/session/sessionService.ts
  • src/main/skill/archive.ts
  • src/main/skill/archiveDownload.ts
  • src/main/skill/index.ts
  • src/main/tool/agentTools/agentBashHandler.ts
  • src/main/tool/agentTools/agentToolManager.ts
  • src/main/tool/index.ts
  • src/main/tool/permission/commandPermissionService.ts
  • src/main/tool/permission/index.ts
  • src/main/tool/permission/toolPermissionBroker.ts
  • src/renderer/api/ApprovalClient.ts
  • src/renderer/src/apps/chat-main/ChatMainApp.vue
  • src/renderer/src/components/cli/CliApprovalDialog.vue
  • src/renderer/src/stores/cliApproval.ts
  • src/shared/contracts/cliCommands.ts
  • src/shared/contracts/common.ts
  • src/shared/contracts/contract.ts
  • src/shared/contracts/events.ts
  • src/shared/contracts/events/approvals.events.ts
  • src/shared/contracts/events/runs.events.ts
  • src/shared/contracts/json.ts
  • src/shared/contracts/localControl.ts
  • src/shared/contracts/routes.ts
  • src/shared/contracts/routes/approvals.routes.ts
  • src/shared/contracts/routes/artifacts.routes.ts
  • src/shared/contracts/routes/audio.routes.ts
  • src/shared/contracts/routes/cli.routes.ts
  • src/shared/contracts/routes/mcp.routes.ts
  • src/shared/contracts/routes/media.routes.ts
  • src/shared/contracts/routes/models.routes.ts
  • src/shared/contracts/routes/ocr.routes.ts
  • src/shared/contracts/routes/providers.routes.ts
  • src/shared/contracts/routes/runs.routes.ts
  • src/shared/contracts/routes/settings.routes.ts
  • src/shared/contracts/routes/skills.routes.ts
  • src/shared/types/agent-interface.d.ts
  • src/shared/types/provider.ts
  • src/shared/types/skill.ts
  • src/shared/utils/filesystem.ts
  • test/main/app/applicationDataReset.test.ts
  • test/main/app/routes.test.ts
  • test/main/approval/approvalBroker.test.ts
  • test/main/approval/routes.test.ts
  • test/main/cli/agentCommandAccess.test.ts
  • test/main/cli/agentTokenAuthority.test.ts
  • test/main/cli/args.test.ts
  • test/main/cli/artifactSpool.test.ts
  • test/main/cli/artifacts.test.ts
  • test/main/cli/auditLog.test.ts
  • test/main/cli/body.test.ts
  • test/main/cli/client.test.ts
  • test/main/cli/computeService.test.ts
  • test/main/cli/descriptor.test.ts
  • test/main/cli/discovery.test.ts
  • test/main/cli/errors.test.ts
  • test/main/cli/inputCapabilityServices.test.ts
  • test/main/cli/launcherService.test.ts
  • test/main/cli/mcpAdminRoutes.test.ts
  • test/main/cli/mediaOutput.test.ts
  • test/main/cli/mutationGuard.test.ts
  • test/main/cli/packagedSmoke.test.ts
  • test/main/cli/policy.test.ts
  • test/main/cli/providerModelAdminRoutes.test.ts
  • test/main/cli/publicText.test.ts
  • test/main/cli/runService.test.ts
  • test/main/cli/server.test.ts
  • test/main/cli/skillService.test.ts
  • test/main/cli/stdin.test.ts
  • test/main/cli/surface.test.ts
  • test/main/cli/transport.test.ts
  • test/main/contracts/localControl.test.ts
  • test/main/desktop/sessionBinding.test.ts
  • test/main/events/typedEventHub.test.ts
  • test/main/notifications/routes.test.ts
  • test/main/ocr/routes.test.ts
  • test/main/orchestration/orchestrationRoutes.test.ts
  • test/main/provider/providerRuntime.test.ts
  • test/main/provider/routes.test.ts
  • test/main/routes/dispatcher.test.ts
  • test/main/routes/routeRegistry.test.ts
  • test/main/scripts/buildCli.test.ts
  • test/main/session/data/tables/deepchatSessionMetadata.test.ts
  • test/main/session/sessionService.test.ts
  • test/main/shared/filesystem.test.ts
  • test/main/skill/archive.test.ts
  • test/main/skill/archiveDownload.test.ts
  • test/main/skill/skillService.test.ts
  • test/main/skill/skillServiceAgentScopes.test.ts
  • test/main/tool/agentTools/agentBashHandler.test.ts
  • test/main/tool/permission/commandPermissionService.test.ts
  • test/main/tool/toolPermissionBroker.test.ts
  • test/renderer/stores/cliApproval.test.ts
  • test/setup.renderer.ts
  • tsconfig.node.json
🚧 Files skipped from review as they are similar to previous changes (139)
  • package.json
  • src/shared/types/agent-interface.d.ts
  • src/main/skill/archiveDownload.ts
  • test/setup.renderer.ts
  • test/main/tool/toolPermissionBroker.test.ts
  • src/renderer/api/ApprovalClient.ts
  • src/shared/contracts/routes/approvals.routes.ts
  • src/main/session/data/tables/deepchatSessionMetadata.ts
  • test/main/orchestration/orchestrationRoutes.test.ts
  • test/main/cli/errors.test.ts
  • tsconfig.node.json
  • src/main/cli/index.ts
  • src/shared/contracts/json.ts
  • test/main/shared/filesystem.test.ts
  • src/shared/utils/filesystem.ts
  • test/main/skill/skillService.test.ts
  • src/main/desktop/sessionBinding.ts
  • src/shared/types/skill.ts
  • src/main/session/routes.ts
  • src/main/app/settingsRoutes.ts
  • src/shared/contracts/events.ts
  • src/renderer/src/components/cli/CliApprovalDialog.vue
  • src/shared/contracts/common.ts
  • src/main/notifications/routes.ts
  • src/main/tool/index.ts
  • test/main/contracts/localControl.test.ts
  • test/main/approval/routes.test.ts
  • test/main/provider/providerRuntime.test.ts
  • src/cli/discovery.ts
  • src/main/app/applicationDataReset.ts
  • src/cli/errors.ts
  • test/main/ocr/routes.test.ts
  • test/main/cli/policy.test.ts
  • resources/acp-registry/registry.json
  • src/main/tool/permission/index.ts
  • test/main/app/routes.test.ts
  • src/main/cli/agentTokenAuthority.ts
  • test/main/notifications/routes.test.ts
  • src/shared/contracts/routes/artifacts.routes.ts
  • src/renderer/src/apps/chat-main/ChatMainApp.vue
  • test/main/routes/routeRegistry.test.ts
  • test/main/cli/stdin.test.ts
  • src/shared/contracts/routes/audio.routes.ts
  • test/main/cli/mediaOutput.test.ts
  • test/main/desktop/sessionBinding.test.ts
  • test/main/cli/artifacts.test.ts
  • test/main/cli/body.test.ts
  • src/shared/contracts/cliCommands.ts
  • src/main/tool/agentTools/agentToolManager.ts
  • src/main/provider/index.ts
  • electron-builder.yml
  • src/shared/contracts/contract.ts
  • src/shared/contracts/routes/media.routes.ts
  • docs/README.md
  • test/main/cli/agentCommandAccess.test.ts
  • test/main/cli/runService.test.ts
  • src/main/routes/index.ts
  • src/main/ocr/ocrRuntimeService.ts
  • src/shared/contracts/routes/settings.routes.ts
  • src/main/session/sessionService.ts
  • src/main/cli/mutationGuard.ts
  • src/main/provider/routes.ts
  • test/main/cli/descriptor.test.ts
  • src/main/notifications/electronWindowNotificationTargets.ts
  • test/main/tool/agentTools/agentBashHandler.test.ts
  • test/main/session/sessionService.test.ts
  • test/main/app/applicationDataReset.test.ts
  • src/main/cli/surface.ts
  • src/main/cli/providerModelAdminRoutes.ts
  • src/main/approval/index.ts
  • scripts/build-cli.mjs
  • src/cli/format.ts
  • src/shared/contracts/events/runs.events.ts
  • src/shared/contracts/routes/runs.routes.ts
  • src/main/cli/ocrService.ts
  • src/main/cli/auditLog.ts
  • test/main/cli/transport.test.ts
  • test/main/cli/client.test.ts
  • src/main/app/routes.ts
  • src/main/cli/body.ts
  • src/shared/contracts/events/approvals.events.ts
  • src/main/ocr/routes.ts
  • src/shared/contracts/routes.ts
  • src/main/cli/policy.ts
  • src/main/cli/errors.ts
  • docs/architecture/local-control-plane/tasks.md
  • test/main/cli/mcpAdminRoutes.test.ts
  • src/cli/stdin.ts
  • src/main/cli/runService.ts
  • test/main/approval/approvalBroker.test.ts
  • test/main/cli/mutationGuard.test.ts
  • src/cli/artifacts.ts
  • src/main/events/typedEventHub.ts
  • src/cli/args.ts
  • src/main/tool/permission/commandPermissionService.ts
  • src/main/mcp/routes.ts
  • src/shared/contracts/routes/models.routes.ts
  • test/main/tool/permission/commandPermissionService.test.ts
  • test/main/routes/dispatcher.test.ts
  • test/main/cli/providerModelAdminRoutes.test.ts
  • src/main/cli/mediaOutput.ts
  • test/main/cli/surface.test.ts
  • test/main/provider/routes.test.ts
  • src/main/cli/computeService.ts
  • src/main/cli/routes.ts
  • test/main/session/data/tables/deepchatSessionMetadata.test.ts
  • src/shared/contracts/localControl.ts
  • src/main/routes/routeRegistry.ts
  • docs/guides/cli.md
  • src/cli/globals.d.ts
  • src/main/desktop/routes.ts
  • src/cli/index.ts
  • src/cli/transport.ts
  • src/shared/contracts/routes/skills.routes.ts
  • src/main/cli/mcpAdminRoutes.ts
  • test/main/cli/server.test.ts
  • test/main/skill/archiveDownload.test.ts
  • src/shared/contracts/routes/cli.routes.ts
  • src/main/cli/launcherService.ts
  • test/main/events/typedEventHub.test.ts
  • test/main/cli/artifactSpool.test.ts
  • src/main/cli/audioTranscriptionService.ts
  • resources/skills/deepchat-cli/SKILL.md
  • src/main/approval/routes.ts
  • test/renderer/stores/cliApproval.test.ts
  • test/main/cli/auditLog.test.ts
  • test/main/cli/args.test.ts
  • src/cli/run.ts
  • src/main/cli/artifactSpool.ts
  • test/main/cli/skillService.test.ts
  • src/shared/contracts/routes/mcp.routes.ts
  • test/main/cli/launcherService.test.ts
  • src/main/cli/artifactRoutes.ts
  • test/main/skill/skillServiceAgentScopes.test.ts
  • src/main/skill/index.ts
  • src/main/approval/approvalBroker.ts
  • src/main/app/composition.ts
  • docs/architecture/local-control-plane/spec.md
  • test/main/cli/computeService.test.ts

Comment thread src/shared/contracts/routes/providers.routes.ts
@yyhhyyyyyy
yyhhyyyyyy merged commit 4b7b03b into dev Aug 6, 2026
15 checks passed
@zhangmo8
zhangmo8 deleted the feat/local-control-plane-cli-v1 branch August 6, 2026 06:56
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