Skip to content

feat: ENG-694 - Daemon Architecture for Background Task Execution (Consolidated) - #770

Merged
mavishay merged 29 commits into
mainfrom
feat/ENG-694-daemon-architecture-consolidated
Mar 23, 2026
Merged

feat: ENG-694 - Daemon Architecture for Background Task Execution (Consolidated)#770
mavishay merged 29 commits into
mainfrom
feat/ENG-694-daemon-architecture-consolidated

Conversation

@mavishay

@mavishay mavishay commented Mar 22, 2026

Copy link
Copy Markdown
Contributor

ENG-694 — Daemon Architecture for Background Task Execution

This consolidated PR merges the best unique contributions from 4 community PRs implementing daemon architecture for background task execution.

Closes

Closes #402
Ref: ENG-694

Original PRs Consolidated


What Was Taken From Each Contributor

🏆 PR #626 — david-mamani — Core JSON-RPC Daemon Infrastructure

The foundational RPC layer that everything else builds on:

  • packages/agent-core/src/common/types/daemon.ts — Complete daemon protocol types (JsonRpcRequest/Response/Notification, DaemonMethodMap, DaemonTransport, ScheduledTask)
  • packages/agent-core/src/daemon/server.ts — DaemonServer: JSON-RPC dispatcher with method registration and notification push
  • packages/agent-core/src/daemon/client.ts — DaemonClient: typed async RPC caller with connection state
  • packages/agent-core/src/daemon/transport.ts — In-process transport pair (Step 2: same-process messaging)
  • packages/agent-core/src/daemon/ipc-transport.ts — Child process IPC transport (Step 3: cross-process via node IPC)
  • packages/agent-core/src/daemon/scheduler.ts — Cron-based task scheduler with 5-field cron parsing
  • apps/desktop/src/main/daemon-bootstrap.ts — Bootstrap lifecycle: tries child-process mode, falls back to in-process
  • apps/desktop/src/main/daemon/cli-bridge.ts — CLI JSON-RPC bridge (nc/pipe interface)
  • apps/desktop/src/main/daemon/entry.ts — Daemon child process entry point
  • apps/desktop/src/main/daemon/service-manager.ts — Auto-start on login service manager
  • apps/desktop/src/main/tray.ts — System tray with active task count, context menu, show/hide window
  • apps/desktop/e2e/specs/daemon.spec.ts — E2E integration tests for daemon
  • Exports added to packages/agent-core/src/index.ts

🎨 PR #613 — SaaiAravindhRaja / ChaiAndCode — UI Panel, Socket Server, Background Mode

User-facing features and database persistence:

  • apps/desktop/src/main/daemon/server.ts — Unix socket / named pipe JSON-RPC server for external clients (nc, CLI tools)
  • apps/desktop/src/main/ipc/task-callbacks.tscreateDaemonTaskCallbacks(): background task execution with desktop notifications when window is hidden
  • apps/desktop/__tests__/unit/main/daemon/scheduler.unit.test.ts — Scheduler unit tests
  • apps/desktop/__tests__/unit/main/daemon/server.unit.test.ts — Socket server unit tests
  • apps/web/src/client/components/settings/DaemonPanel.tsx — Settings UI: background mode toggle + socket path display with copy button and CLI usage examples
  • apps/web/src/client/components/ui/switch.tsx — Accessible Switch UI component
  • apps/web/locales/en/settings.json + zh-CN/settings.json — i18n: 'Daemon' tab label
  • apps/web/src/client/components/layout/SettingsDialog.tsx — Add Daemon tab to settings
  • apps/web/src/client/lib/accomplish.ts — Add daemon API interface methods
  • apps/desktop/src/preload/index.ts — Expose getRunInBackground, setRunInBackground, getDaemonSocketPath to renderer
  • apps/desktop/src/main/ipc/handlers/settings-handlers.ts — IPC handlers for daemon settings
  • packages/agent-core/src/storage/migrations/v013-daemon.ts — DB migration: add run_in_background column
  • Storage repositories and type extensions for runInBackground

🔧 PR #598 — aryan877 — HTTP Daemon App with Rate Limiting & Services

Standalone daemon app with security and observability:

  • apps/daemon/ — New standalone @accomplish/daemon package
  • apps/daemon/src/http-server-factory.ts — HTTP server with timing-safe Bearer token auth, 1MB body limit, route dispatch
  • apps/daemon/src/rate-limiter.ts — Sliding window rate limiter per IP with automatic cleanup (unique security feature)
  • apps/daemon/src/health.ts — Health check endpoint
  • apps/daemon/src/permission-service.ts — Permission request management
  • apps/daemon/src/storage-service.ts — Persistent storage service
  • apps/daemon/src/task-service.ts — Task lifecycle management
  • apps/daemon/src/thought-stream-service.ts — Thought stream event broadcasting
  • Unit tests for all services
  • docs/architecture.md — Architecture documentation

🔌 PR #509 — Eshaan-byte — WebSocket Support & PID Management

Real-time event streaming and process lifecycle:

  • apps/daemon/src/websocket.ts — WebSocket server on /ws path for real-time daemon event streaming (task updates, permission requests, auth errors)
  • apps/daemon/src/pid.ts — PID file management with stale lock detection and automatic cleanup
  • apps/daemon/src/mcp-bridges.ts — MCP tool bridges for daemon communication
  • apps/daemon/src/daemon-options.ts — Daemon configuration options

Architecture Overview

The daemon architecture has two layers:

  1. In-Electron Layer (packages/agent-core/src/daemon/ + apps/desktop/src/main/daemon/):

    • JSON-RPC 2.0 transport between Electron main process and renderer/external clients
    • Unix socket server for external CLI/tool access
    • System tray for background mode (window hidden but app alive)
    • Cron-based task scheduling
  2. Standalone Daemon (apps/daemon/):

    • Separate HTTP process for always-on background task execution
    • Rate limiting, auth tokens, health checks
    • WebSocket for real-time event streaming
    • PID file management

Key Design Decisions

  • No pnpm-lock.yaml changes — avoided to prevent merge conflicts with main
  • Migration version 13 — v009-daemon renamed to v013 to avoid conflict with existing v009-favorites
  • Progressive architecture — in-process fallback → child-process IPC → standalone daemon
  • Attribution preserved — each contributor's code identifiable by commit and file ownership

Summary by CodeRabbit

  • New Features

    • Background daemon with system tray, Run in Background toggle, copyable daemon socket path, CLI commands, and cron-based scheduled tasks.
  • Enhancements

    • Tray UX improvements, OS notifications for task completion/failure, graceful startup/shutdown, and clearer health/status reporting for background tasks.
  • Tests

    • New unit and E2E tests covering scheduler, HTTP APIs, rate limiting, RPC/daemon client, and background execution.
  • Documentation

    • Architecture doc updated and new localization keys for Daemon settings.

mavishay added 3 commits March 22, 2026 16:49
From PR #626 by david-mamani (David Abdiel Mamani Chuquimamani):
- packages/agent-core/src/common/types/daemon.ts: daemon protocol types
- packages/agent-core/src/daemon/server.ts: DaemonServer JSON-RPC dispatcher
- packages/agent-core/src/daemon/client.ts: DaemonClient typed RPC caller
- packages/agent-core/src/daemon/transport.ts: in-process transport pair
- packages/agent-core/src/daemon/ipc-transport.ts: child_process IPC transport
- packages/agent-core/src/daemon/scheduler.ts: cron-based task scheduler
- packages/agent-core/src/daemon/index.ts: public exports
- apps/desktop/src/main/daemon-bootstrap.ts: bootstrap daemon lifecycle
- apps/desktop/src/main/daemon/cli-bridge.ts: CLI JSON-RPC bridge
- apps/desktop/src/main/daemon/entry.ts: daemon entry point
- apps/desktop/src/main/daemon/service-manager.ts: auto-start service
- apps/desktop/src/main/tray.ts: system tray with task count
- apps/desktop/e2e/specs/daemon.spec.ts: E2E tests

Closes #402
Ref: ENG-694
…694)

From PR #613 by SaaiAravindhRaja / ChaiAndCode:
- apps/desktop/src/main/daemon/server.ts: Unix socket / named pipe server
  with JSON-RPC 2.0, supports daemon.ping, task.start, task.schedule, etc.
- apps/desktop/src/main/ipc/task-callbacks.ts: add DaemonTaskCallbacksOptions
  and createDaemonTaskCallbacks() for background task execution with system
  notifications when window is hidden
- apps/desktop/__tests__/unit/main/daemon/scheduler.unit.test.ts: scheduler tests
- apps/desktop/__tests__/unit/main/daemon/server.unit.test.ts: server unit tests
- apps/web/src/client/components/settings/DaemonPanel.tsx: Settings UI panel
  with background mode toggle and socket path display
- apps/web/src/client/components/ui/switch.tsx: accessible Switch component
- apps/web/locales/en|zh-CN/settings.json: add 'daemon' tab i18n key
- apps/web/src/client/components/layout/SettingsDialog.tsx: add Daemon tab
- apps/web/src/client/lib/accomplish.ts: add daemon API interface methods
- apps/desktop/src/preload/index.ts: expose daemon IPC methods to renderer
- apps/desktop/src/main/index.ts: integrate daemon bootstrap + tray init
- apps/desktop/src/main/ipc/handlers/settings-handlers.ts: IPC handlers
  for daemon:get-run-in-background, daemon:set-run-in-background, daemon:get-socket-path
- packages/agent-core/src/storage/migrations/v013-daemon.ts: DB migration
  to add run_in_background column to app_settings
- packages/agent-core/src/storage/repositories/appSettings.ts: add
  getRunInBackground / setRunInBackground CRUD
- packages/agent-core/src/types/storage.ts: extend AppSettings + AppSettingsAPI

Ref: ENG-694
…ces (ENG-694)

From PR #598 by aryan877 (Aryan):
- apps/daemon/package.json: @accomplish/daemon standalone app config
- apps/daemon/src/index.ts: daemon entry point with HTTP server
- apps/daemon/src/cli.ts: CLI argument parsing and startup
- apps/daemon/src/health.ts: health check endpoint (/health)
- apps/daemon/src/http-server-factory.ts: HTTP server with auth token
  validation (timing-safe), request size limits, route dispatch
- apps/daemon/src/rate-limiter.ts: sliding window rate limiter per IP
- apps/daemon/src/permission-service.ts: permission request management
- apps/daemon/src/storage-service.ts: persistent storage service
- apps/daemon/src/task-service.ts: task creation and management
- apps/daemon/src/thought-stream-service.ts: thought stream events
- apps/daemon/tsconfig.json: TypeScript config for daemon app
- apps/daemon/tsup.config.ts: tsup build config
- apps/daemon/vitest.config.ts: vitest test config
- apps/daemon/__tests__/unit/health.test.ts: health endpoint tests
- apps/daemon/__tests__/unit/http-server-factory.test.ts: server factory tests
- apps/daemon/__tests__/unit/rate-limiter.test.ts: rate limiter tests
- docs/architecture.md: daemon architecture documentation

Key features: Bearer token auth, 1MB request body limit, 429 rate limiting,
per-IP sliding window with automatic cleanup.

Ref: ENG-694
@orcaman

orcaman commented Mar 22, 2026

Copy link
Copy Markdown
Contributor

Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues
Licenses 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@coderabbitai

coderabbitai Bot commented Mar 22, 2026

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 593a51b0-35d9-4e48-9971-aa09fc068aec

📥 Commits

Reviewing files that changed from the base of the PR and between f3f7fbc and 849a3ca.

📒 Files selected for processing (3)
  • apps/desktop/__tests__/unit/main/daemon/server.unit.test.ts
  • apps/web/locales/en/settings.json
  • apps/web/locales/zh-CN/settings.json
✅ Files skipped from review due to trivial changes (3)
  • apps/web/locales/en/settings.json
  • apps/web/locales/zh-CN/settings.json
  • apps/desktop/tests/unit/main/daemon/server.unit.test.ts

📝 Walkthrough

Walkthrough

Adds a daemon subsystem and desktop integration: JSON‑RPC protocol, daemon server/client transports (in‑process and IPC), PID locking, scheduler, HTTP/WebSocket endpoints, task/permission/thought services, storage migration/settings for background run, desktop bootstrap/spawn/fallback, tray/CLI/UI settings, tests, and build/test configs.

Changes

Cohort / File(s) Summary
Daemon runtime & services
apps/daemon/src/index.ts, apps/daemon/src/task-service.ts, apps/daemon/src/storage-service.ts, apps/daemon/src/health.ts, apps/daemon/src/daemon-options.ts
New daemon entrypoint and supporting services: task manager wrapper, storage lifecycle, health reporting, CLI/options wiring, startup/shutdown orchestration, RPC registration and task lifecycle plumbing.
HTTP servers & APIs
apps/daemon/src/http-server-factory.ts, apps/daemon/src/permission-service.ts, apps/daemon/src/thought-stream-service.ts, apps/daemon/src/mcp-bridges.ts
HTTP server factory with auth, per‑IP rate limiting, body parsing/validation, route dispatch; permission/question and thought/checkpoint APIs; MCP↔WebSocket bridges.
Daemon infra, utils & tests
apps/daemon/src/pid.ts, apps/daemon/src/rate-limiter.ts, apps/daemon/src/websocket.ts, apps/daemon/__tests__/*, apps/daemon/package.json, apps/daemon/tsconfig.json, apps/daemon/tsup.config.ts, apps/daemon/vitest.config.ts
PID helpers, RateLimiter, WebSocket utilities, unit tests (health/http/rate-limiter), and daemon package/build/test configs.
Agent-core: daemon protocol & runtime
packages/agent-core/src/common/types/daemon.ts, packages/agent-core/src/daemon/{types,client,server,transport,ipc-transport,logger,pid-lock,socket-path,crash-handlers}.ts, packages/agent-core/src/daemon/index.ts
Typed JSON‑RPC types/constants, DaemonServer/DaemonClient, in‑process and IPC transports, PID lock, socket/path helpers, crash handlers, logger, and public re-exports.
Agent-core: scheduler & storage
packages/agent-core/src/daemon/scheduler.ts, packages/agent-core/src/storage/migrations/v013-daemon.ts, packages/agent-core/src/factories/storage.ts, packages/agent-core/src/storage/repositories/*, packages/agent-core/src/types/storage.ts, packages/agent-core/src/storage/migrations/index.ts
In‑memory cron scheduler and APIs; DB migration v013 adding run_in_background; storage repo/API getters/setters and types for runInBackground; migration index bump.
Desktop: daemon wiring & lifecycle
apps/desktop/src/main/daemon-bootstrap.ts, apps/desktop/src/main/daemon/daemon-inprocess*.ts, apps/desktop/src/main/daemon/daemon-spawn.ts, apps/desktop/src/main/daemon/daemon-lifecycle.ts, apps/desktop/src/main/daemon/entry.ts, apps/desktop/src/main/daemon/server.ts, apps/desktop/src/main/daemon/rpc-dispatcher.ts
Desktop-side daemon wiring: child-process spawn with IPC, fallback to in-process transport, JSON‑RPC newline socket server, dispatcher, handler registration, and lifecycle state management.
Desktop: CLI, auto-start, tray & callbacks
apps/desktop/src/main/daemon/cli-bridge.ts, apps/desktop/src/main/daemon/service-manager.ts, apps/desktop/src/main/tray.ts, apps/desktop/src/main/ipc/task-callbacks.ts, apps/desktop/src/main/index.ts
CLI command bridge, OS auto-start enable/disable, tray lifecycle/menu and updates, daemon-aware task callbacks, and integration into app startup/shutdown.
Desktop tests & helpers
apps/desktop/__tests__/unit/main/daemon/*.test.ts, apps/desktop/__tests__/unit/main/daemon/helpers/server.helpers.ts, apps/desktop/e2e/specs/daemon.spec.ts
Unit and e2e tests for scheduler, daemon server/IPC behavior, and end‑to‑end background execution flow; test helpers for socket interactions.
Web UI: settings, preload, switch UI & locales
apps/web/src/client/components/settings/DaemonPanel.tsx, apps/web/src/client/components/layout/SettingsDialog.tsx, apps/web/src/client/components/ui/switch.tsx, apps/web/src/client/lib/accomplish.ts, apps/web/locales/*, apps/desktop/src/preload/index.ts, apps/web/package.json
Daemon settings panel and tab, new Switch UI component, preload IPC methods (get/set runInBackground, socket path), locale keys, and added Radix Switch dependency.
Docs & build tweaks
docs/architecture.md, apps/desktop/e2e/docker/Dockerfile
Architecture document trimmed/updated and Dockerfile ensures daemon package.json is included during image dependency install.

Sequence Diagram(s)

sequenceDiagram
    participant UI as Electron UI
    participant Client as DaemonClient
    participant Daemon as Daemon Process
    participant Storage as Storage
    participant TaskMgr as Task Manager

    UI->>Client: JSON‑RPC request (task.start)
    Client->>Daemon: deliver request
    Daemon->>Storage: persist placeholder task
    Daemon->>TaskMgr: start task
    TaskMgr-->>Daemon: progress / messages
    Daemon->>Storage: persist progress/status
    Daemon-->>Client: notify UI (task.progress)
    TaskMgr-->>Daemon: complete
    Daemon->>Storage: mark complete
    Daemon-->>Client: notify UI (task.complete)
Loading
sequenceDiagram
    participant Desktop as Electron Main
    participant Child as Forked Child
    participant IPC as Node IPC
    participant InProc as In‑Process Transport

    Desktop->>Child: fork daemon entry (daemon:init)
    Child->>Desktop: IPC "daemon:ready"
    alt Child ready within timeout
        Desktop->>IPC: create IPC transport → DaemonClient
    else fork fails or times out
        Desktop->>InProc: create in‑process transport pair and bootstrap daemon in‑process
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • mcmatan
  • hairozen
  • orcaman

Poem

🐇
I tunneled bytes where daemons hum and run,
I mind the queue when windows go to sun,
I whisper RPCs through pipes and cords,
I guard your tasks and carry all the boards,
A hopping helper — always up, well done.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/ENG-694-daemon-architecture-consolidated

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

Note

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

🟠 Major comments (19)
packages/agent-core/src/daemon/scheduler.ts-112-112 (1)

112-112: 🛠️ Refactor suggestion | 🟠 Major

Replace console.log with the application's logger.

Production code should use the app's existing logger instead of console.log. This applies to all logging statements in this file (lines 112, 134, 155, 166, 173, 186, 194). As per coding guidelines: "No console.log in production code — use the app's existing logger."

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

In `@packages/agent-core/src/daemon/scheduler.ts` at line 112, Replace all
console.log calls in this file with the application's logger: import or reuse
the existing logger instance (e.g., logger or processLogger) and replace
console.log(...) with logger.info(...) for normal events and logger.error(...)
for errors, preserving the original message text and interpolated values (e.g.,
id, cron, prompt, err). Update every logging site in this module (the places
that currently log schedule additions, removals, execution starts/completions,
and errors—e.g., inside functions handling addSchedule, removeSchedule,
runSchedule/runDueSchedules/stopSchedule) so they use the app logger and remove
any remaining console.* usages. Ensure the messages retain the same context and
values so behavior and searchable logs remain consistent.
apps/web/src/client/components/ui/switch.tsx-1-40 (1)

1-40: ⚠️ Potential issue | 🟠 Major

Refactor to use @radix-ui/react-switch with CVA variants to match the project's UI component pattern.

The custom Switch implementation should follow the established pattern used by other UI components in the codebase (label, avatar, button, etc.), which wrap Radix UI primitives with class-variance-authority for variants. Add @radix-ui/react-switch as a dependency and refactor this component to import from it, similar to how label.tsx uses @radix-ui/react-label. This ensures consistency across the component library and leverages Radix UI's accessibility features.

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

In `@apps/web/src/client/components/ui/switch.tsx` around lines 1 - 40, Replace
the custom button-based Switch with a Radix-based wrapper following the
project's CVA variant pattern: add `@radix-ui/react-switch` dependency, create a
Radix Switch root and thumb wrapper that map the current props (checked,
onChange, disabled, ariaLabel, className) and export the same Switch component
interface (SwitchProps) so consuming code is unchanged; use
class-variance-authority to define variants for the root and thumb (matching
existing tokens like bg-primary/bg-muted, translate-x classes, focus/disabled
styles) and ensure event handlers use Radix's onCheckedChange to call the
provided onChange; update imports/exports so the component exports the same
Switch function name and prop types.
apps/daemon/vitest.config.ts-7-7 (1)

7-7: ⚠️ Potential issue | 🟠 Major

Replace __dirname with ESM-safe path resolution.

The daemon package is configured as ESM ("type": "module"), so __dirname is undefined and will cause a ReferenceError at runtime, breaking Vitest startup.

🔧 Proposed fix
-import { defineConfig } from 'vitest/config';
-import path from 'path';
+import { defineConfig } from 'vitest/config';
+import { dirname, resolve } from 'node:path';
+import { fileURLToPath } from 'node:url';
+
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = dirname(__filename);

 export default defineConfig({
   resolve: {
     alias: {
-      '@accomplish_ai/agent-core': path.resolve(__dirname, '../../packages/agent-core/src'),
+      '@accomplish_ai/agent-core': resolve(__dirname, '../../packages/agent-core/src'),
     },
   },
   test: {
     globals: true,
     root: __dirname,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/daemon/vitest.config.ts` at line 7, The config uses
path.resolve(__dirname, '../../packages/agent-core/src') which fails in ESM
where __dirname is undefined; replace the __dirname usage with an ESM-safe
resolution using import.meta.url (e.g. derive a dir with
fileURLToPath(import.meta.url) + path.dirname, or use new
URL('../../packages/agent-core/src', import.meta.url) and convert to a
filesystem path) so the mapping for '@accomplish_ai/agent-core' is resolved
correctly in vitest.config.ts; update the expression that calls path.resolve
(and import or use fileURLToPath/import.meta.url) accordingly.
apps/desktop/__tests__/unit/main/daemon/server.unit.test.ts-1-241 (1)

1-241: 🛠️ Refactor suggestion | 🟠 Major

Split this spec to stay under the new-file size limit.

At 241 lines, this new file already exceeds the repo cap. Extracting sendJsonRpc() or moving lifecycle/error-path cases into a second spec would get it back under the limit and keep future additions manageable. As per coding guidelines !(packages/agent-core/src/storage/migrations)/**/*.{js,ts,tsx}: New files must be < 200 lines — split into logical modules if needed (exceptions: generated files, migrations).

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

In `@apps/desktop/__tests__/unit/main/daemon/server.unit.test.ts` around lines 1 -
241, The test file exceeds the new-file size limit; split it by extracting the
reusable sendJsonRpc helper and/or some lifecycle/error-path tests into a second
spec file so each new test file is under 200 lines. Concretely, move the
sendJsonRpc(...) function into a test utility module (e.g.,
test/utils/sendJsonRpc) and import it in this spec, and/or relocate a subset of
tests (for example the parse-error, notification, and handler-error cases or the
idempotency/lifecycle tests) into a new spec file that imports
registerMethod/startDaemonServer/stopDaemonServer/getSocketPath; ensure the new
files keep the same describe blocks and vi.mock setup so tests run unchanged.
apps/daemon/src/storage-service.ts-21-30 (1)

21-30: ⚠️ Potential issue | 🟠 Major

Only publish the storage handle after initialize() succeeds.

If storage.initialize() throws here, this.storage stays non-null and later getStorage() / close() calls will operate on a partially opened instance. That makes recovery after a failed open or migration unreliable.

💡 Safer initialization pattern
-    this.storage = createStorage({
+    const storage = createStorage({
       databasePath,
       runMigrations: true,
       userDataPath: dir,
       secureStorageFileName: dataDir ? 'secure-storage.json' : 'secure-storage-dev.json',
     });
 
-    this.storage.initialize();
+    storage.initialize();
+    this.storage = storage;
     console.log(`[StorageService] Database initialized at ${databasePath}`);
-    return this.storage;
+    return storage;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/daemon/src/storage-service.ts` around lines 21 - 30, The current code
assigns this.storage from createStorage(...) before calling
this.storage.initialize(), which can leave a partially-initialized handle if
initialize() throws; change to create a local variable (e.g., const storage =
createStorage(...)), call await storage.initialize() (or handle the returned
promise) and only after initialize() succeeds assign this.storage = storage;
also ensure getStorage() and close() continue to guard against null/undefined
storage references (they already exist but verify behavior remains correct).
apps/desktop/src/main/index.ts-342-349 (1)

342-349: ⚠️ Potential issue | 🟠 Major

Gate tray persistence on the actual background-mode setting.

This path now always converts window close into hide(), and window-all-closed never quits. That makes the new “Run in Background” setting effectively cosmetic: users can disable it in settings and still end up with the app resident in the tray.

Also applies to: 365-368

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

In `@apps/desktop/src/main/index.ts` around lines 342 - 349, The close handler
currently always prevents close and hides the window, ignoring the user's "Run
in Background" setting; update the logic in the mainWindow 'close' listener to
check the real background-mode flag (e.g., a settings getter like
getRunInBackground() or appSettings.runInBackground) before calling
event.preventDefault() and mainWindow.hide(), and similarly adjust the
'window-all-closed' handler to only call app.quit() when the background-mode is
disabled; reference the existing symbols mainWindow, isQuitting, and the
window-all-closed handler so the change gates hiding/quitting on the actual
setting.
apps/desktop/src/main/tray.ts-103-105 (1)

103-105: ⚠️ Potential issue | 🟠 Major

updateTray() drops the window reference.

Rebuilding the menu with buildContextMenu(null) means the "Show Accomplish" entry stops being able to reopen/focus the app after the first refresh. Keep the active BrowserWindow in module state and reuse it when updating the menu.

💡 Preserve the live window when refreshing the tray menu
 let tray: Tray | null = null;
+let trayWindow: BrowserWindow | null = null;
 let activeTaskCount = 0;
@@
 export function createTray(mainWindow: BrowserWindow | null): Tray {
+  trayWindow = mainWindow;
   const iconPath = getIconPath();
@@
 export function updateTray(): void {
   if (tray && !tray.isDestroyed()) {
-    tray.setContextMenu(buildContextMenu(null));
+    tray.setContextMenu(buildContextMenu(trayWindow));
   }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/main/tray.ts` around lines 103 - 105, updateTray currently
calls buildContextMenu(null) which drops the live BrowserWindow reference so the
"Show Accomplish" menu item can't refocus the app; instead keep a module-level
BrowserWindow variable (e.g., mainWindow or activeWindow) that is set when the
window is created and cleared on close, and change updateTray to call
buildContextMenu(activeWindow) (and ensure buildContextMenu accepts a
BrowserWindow | null). Also update any window-creation/close handlers to
maintain this module state so tray.setContextMenu always receives the live
window reference.
packages/agent-core/src/daemon/ipc-transport.ts-52-56 (1)

52-56: ⚠️ Potential issue | 🟠 Major

Don't ignore IPC send failure/backpressure.

Both child.send() (line 55) and process.send() (line 93) return a boolean indicating whether the message was successfully queued. This transport ignores the return value, treating every call as delivered. When the IPC message queue fills up (especially during daemon shutdown or restart), send() returns false, silently dropping the JSON-RPC request or response and leaving the peer stranded until a higher-level timeout fires.

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

In `@packages/agent-core/src/daemon/ipc-transport.ts` around lines 52 - 56, The
transport currently ignores the boolean return of child.send/process.send, so
when IPC backpressure occurs messages are silently dropped; modify the
send(message: JsonRpcMessage) and the analogous parent-send path to check the
returned boolean and, if false, push the DaemonIpcEnvelope onto an internal
outgoing queue and attach a one-time 'drain' handler on the corresponding IPC
object (child or process) to flush the queue in order; also guard against
disconnected peers by dropping queued messages with an error or invoking a
callback if the socket closes. Use the existing send method, DaemonIpcEnvelope,
child.send and process.send identifiers to locate places to add the queue,
push-on-false logic, and the 'drain' flush implementation.
apps/daemon/src/task-service.ts-295-318 (1)

295-318: ⚠️ Potential issue | 🟠 Major

Carry the configured OpenAI base URL into daemon task env.

This environment builder forwards Bedrock and Ollama settings but omits storage.getOpenAiBaseUrl(). Tasks running through the daemon path will ignore a user-configured OpenAI-compatible endpoint and fall back to the default API.

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

In `@apps/daemon/src/task-service.ts` around lines 295 - 318, In buildEnvironment
add the configured OpenAI base URL into the environment config so daemon tasks
use a user-specified OpenAI-compatible endpoint: call storage.getOpenAiBaseUrl()
inside buildEnvironment and include its value (e.g. openAiBaseUrl or
openaiBaseUrl) on the EnvironmentConfig object passed to
buildOpenCodeEnvironment; update the EnvironmentConfig shape if needed and
ensure buildOpenCodeEnvironment consumes that new field so tasks honor the
configured OpenAI base URL (references: buildEnvironment,
storage.getOpenAiBaseUrl, EnvironmentConfig, buildOpenCodeEnvironment).
apps/daemon/src/task-service.ts-189-191 (1)

189-191: ⚠️ Potential issue | 🟠 Major

Do not stamp completedAt on non-terminal status changes.

Both the resume path and the generic onStatusChange callback pass new Date().toISOString() regardless of the new status. That makes queued/running tasks look completed and corrupts completion-based sorting and duration logic.

Also applies to: 271-274

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

In `@apps/daemon/src/task-service.ts` around lines 189 - 191, The current call to
this.storage.updateTaskStatus(existingTaskId, task.status, new
Date().toISOString()) stamps completedAt for every status change; change it so
completedAt is only set when task.status is a terminal state (e.g., "completed",
"failed", "canceled")—compute completedAt =
terminalStatuses.includes(task.status) ? new Date().toISOString() :
null/undefined and pass that value to this.storage.updateTaskStatus, and apply
the same conditional logic in the resume path and the onStatusChange callback
locations referenced (the other updateTaskStatus call around lines 271-274).
apps/desktop/src/main/daemon/server.ts-155-163 (1)

155-163: ⚠️ Potential issue | 🟠 Major

Only delete daemon.sock after proving it is stale.

Unconditionally unlinking an existing socket lets a second daemon instance take over the path even if the first daemon is still alive, leaving the original process running but unreachable. This cleanup should only happen after a failed connect/health check proves the file is orphaned.

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

In `@apps/desktop/src/main/daemon/server.ts` around lines 155 - 163, Replace the
unconditional removal of socketPath with a probe: attempt a short TCP/IPC
connection to socketPath (use net.createConnection / net.connect) and set a
short timeout; if the probe connects (on 'connect'), immediately close the
connection and do not unlink the file; if the probe fails with a
connection-refused/ECONNREFUSED/ENOENT/ENOENT-like error or times out, then
treat the socket as stale and call fs.unlinkSync(socketPath); handle other
errors by logging and not unlinking blindly. Update the block that currently
uses fs.existsSync/fs.unlinkSync to use this probe logic and reference
socketPath and the probe connection's event handlers to decide when to unlink.
apps/daemon/src/task-service.ts-117-128 (1)

117-128: ⚠️ Potential issue | 🟠 Major

Save the task before handing control to the task manager.

startTask() registers callbacks and awaits taskManager.startTask() before the task is persisted. If the manager emits status/messages synchronously or very quickly, those callbacks can hit storage before the task row exists and the earliest state gets lost.

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

In `@apps/daemon/src/task-service.ts` around lines 117 - 128, The task is saved
after calling taskManager.startTask which can cause race conditions; instead
construct the Task object (set task.messages with the initialUserMessage), call
this.storage.saveTask(task) before invoking this.taskManager.startTask, and then
pass the saved task (or its id) into startTask; keep createCallbacks(taskId)
as-is but ensure callbacks reference the existing persisted task row so
synchronous callbacks won't run against a non-existent task.
apps/desktop/src/main/daemon/server.ts-89-116 (1)

89-116: ⚠️ Potential issue | 🟠 Major

Validate the JSON-RPC envelope before dispatch.

Right now any non-array object is cast to JsonRpcRequest. Payloads with a missing/invalid jsonrpc, a non-string method, or a bad id can be silently dropped as “notifications” or downgraded to Method not found instead of returning -32600 Invalid Request, which will make client bugs very hard to diagnose.

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

In `@apps/desktop/src/main/daemon/server.ts` around lines 89 - 116, The request
envelope isn't being fully validated before casting to JsonRpcRequest; add
explicit checks after parsing to ensure parsed.jsonrpc === '2.0', parsed.method
is a string, and parsed.id (if present) is either string, number, or null
(allowing absence for notifications). If any of these fail, send a JSON-RPC
-32600 Invalid Request response (id null) rather than treating it as a
notification or falling through to methodHandlers; keep using
isNotification(request) and methodHandlers.get(method) after the envelope passes
validation. This validation should reference the same variables/types used now
(parsed, JsonRpcRequest, isNotification, methodHandlers, id, method, params).
apps/desktop/src/main/daemon-bootstrap.ts-84-117 (1)

84-117: ⚠️ Potential issue | 🟠 Major

Fail fast when the child exits before daemon:ready.

The exit handler only logs and clears daemonProcess, so an early crash keeps the bootstrap promise pending until the 10s timeout fires. Rejecting there immediately will let startup fall back without the extra delay and with a more accurate error.

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

In `@apps/desktop/src/main/daemon-bootstrap.ts` around lines 84 - 117, The exit
handler for daemonProcess currently only logs and clears daemonProcess, leaving
the bootstrap Promise pending; update the daemonProcess.on('exit', ...) callback
to clear the readiness timeout and reject the Promise immediately (e.g., call
clearTimeout(timer); reject(new Error(`Daemon exited before ready (code
${code})`)); set daemonProcess = null) so the bootstrap fails fast instead of
waiting for DAEMON_READY_TIMEOUT_MS; ensure you only reject when the Promise is
still pending (introduce a local "settled" boolean toggled when resolve/reject
are called, or check for timer existence) to avoid unhandled rejections.
apps/daemon/src/mcp-bridges.ts-54-58 (1)

54-58: ⚠️ Potential issue | 🟠 Major

Do not expose these loopback control endpoints with Access-Control-Allow-Origin: *.

These servers have no auth and they drive permission/question flows for the active task. With permissive CORS, any website opened in the user's browser can fetch() localhost and trigger prompts or read back question responses. Remove CORS for this internal bridge or require an auth token/origin allowlist.

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

In `@apps/daemon/src/mcp-bridges.ts` around lines 54 - 58, The setCors function
currently sets Access-Control-Allow-Origin: * which exposes internal loopback
control endpoints; change setCors to stop sending a wildcard origin and instead
enforce a safe policy: either remove CORS headers entirely for these internal
bridges or implement an origin allowlist check (compare req.headers.origin
against a configured whitelist) and only set Access-Control-Allow-Origin to that
trusted origin, and/or require a server-side auth token (e.g., check an
Authorization header) before responding; update the setCors usage (and any
handlers that call setCors) to perform this allowlist/auth check so unauthorized
browser origins cannot access these endpoints.
apps/daemon/src/daemon-options.ts-31-39 (1)

31-39: ⚠️ Potential issue | 🟠 Major

The CLI fallback is not valid on Windows.

When resolveCliPath() misses, this code falls back to .../bin/opencode or bare opencode. The Windows task runner in this repo now requires a real .exe path, so this branch guarantees daemon task launches fail there instead of degrading gracefully.

Based on learnings: In packages/agent-core/src/internal/classes/OpenCodeAdapter.ts, Windows now requires CLI commands to resolve to a real .exe path and fails fast for non-.exe commands.

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

In `@apps/daemon/src/daemon-options.ts` around lines 31 - 39, getCliCommand
currently falls back to non-executable names which breaks Windows; update
getCliCommand to detect Windows (process.platform === 'win32') and prefer real
executable paths: check for the Windows variants (e.g., path.join(DAEMON_ROOT,
'node_modules', 'opencode-ai', 'bin', 'opencode.exe') and 'opencode.cmd') and
also check node_modules/.bin/opencode.cmd or .exe using fs.existsSync, returning
the first real .exe/.cmd path found; if none found on Windows, return a
platform-appropriate default (e.g., 'opencode.exe' or 'opencode.cmd') instead of
the bare 'opencode', while retaining the existing behavior on non-Windows
platforms.
packages/agent-core/src/daemon/types.ts-120-184 (1)

120-184: ⚠️ Potential issue | 🟠 Major

Keep one canonical daemon protocol definition.

This file diverges from packages/agent-core/src/common/types/daemon.ts on method names/results (task.stop vs task.cancel, health.check vs daemon.ping, different notification keys). Leaving both public maps around lets callers type-check against a protocol the runtime does not implement.

Based on learnings: Place shared types in packages/agent-core/src/common/types/.

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

In `@packages/agent-core/src/daemon/types.ts` around lines 120 - 184, The
RpcMethodMap, RpcNotificationMap and related types in this file diverge from the
canonical daemon protocol in common/types/daemon.ts; remove the duplicate local
protocol and instead import and re-export the single shared definitions (e.g.
RpcMethodMap, RpcMethod, RpcNotificationMap, RpcNotificationType) from
packages/agent-core/src/common/types/daemon.ts, and update any differing
keys/names to match the canonical names (e.g. rename 'task.stop' to the
canonical 'task.cancel', change 'health.check' to 'daemon.ping', and align
notification keys like 'task.statusChange' etc. so callers type-check against
the runtime protocol). Ensure all references in this file use the imported
symbols rather than redeclaring them.
apps/daemon/src/permission-service.ts-33-39 (1)

33-39: ⚠️ Potential issue | 🟠 Major

A single global getActiveTaskId() is not enough for concurrent tasks.

Both endpoints stamp requests with whatever task ID this getter returns at that moment. Since the daemon is configured elsewhere in this PR for multiple concurrent tasks, a permission/question emitted by task A can be attributed to task B. The request payload or transport context needs a task-scoped identifier.

Also applies to: 80-88, 135-145

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

In `@apps/daemon/src/permission-service.ts` around lines 33 - 39, The current init
registers a single global getActiveTaskId and onPermissionRequest causing
cross-task attribution; change the APIs so each emitted permission/question
carries an explicit task-scoped id: update init to accept a task-scoped getter
or make permissionRequestHandler signature require a taskId parameter (e.g.,
permissionRequestHandler(request: unknown, taskId: string)), and modify all emit
points that call getActiveTaskId/onPermissionRequest (including the code paths
referenced by getActiveTaskId and onPermissionRequest) to pass through the
originating taskId from the request or transport context instead of relying on a
global getter; ensure the request payload or transport envelope is extended to
include this taskId and the permission emission/stamping logic uses that field.
apps/daemon/src/mcp-bridges.ts-65-74 (1)

65-74: ⚠️ Potential issue | 🟠 Major

Catch rejected async route handlers inside createMcpServer.

createMcpServer() invokes handler(req, res) at line 70 without awaiting or catching rejections, but all three call sites (lines 92, 114, 135) pass async handlers. The type signature declares handler: (...) => void, but async functions return Promise<void>. Any error thrown in the handler—from parseBody (line 58: JSON.parse can throw on malformed JSON), await promise waits, or any async operation—becomes an unhandled rejection. The client receives no response and hangs.

Wrap the handler(req, res) call in a .catch() or top-level try/catch to send an error response:

handler(req, res).catch(err => {
  console.error(`[${label}] Handler error:`, err);
  if (!res.headersSent) {
    res.writeHead(500);
    res.end(JSON.stringify({ error: 'Internal server error' }));
  }
});

Or update the type signature to handler: (...) => Promise<void> and await it, then add error handling in the HTTP server wrapper.

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

In `@apps/daemon/src/mcp-bridges.ts` around lines 65 - 74, createMcpServer
currently calls handler(req, res) without handling Promise rejections (handler
is async at the call sites), so update the signature to handler: (req:
http.IncomingMessage, res: http.ServerResponse) => Promise<void> and in the
http.createServer wrapper await the call and catch errors (or call
handler(...).catch(...)); on error log with the label (use `[${label}]`) and if
!res.headersSent send a 500 JSON error and end the response so the client
doesn't hang; reference createMcpServer and the handler invocation when making
the change.
🟡 Minor comments (5)
apps/daemon/src/pid.ts-35-35 (1)

35-35: ⚠️ Potential issue | 🟡 Minor

Replace console.log with the app's logger.

The coding guidelines specify no console.log in production code — use the app's existing logger instead. This file uses console.log for PID file operations logging.

As per coding guidelines: "No console.log in production code — use the app's existing logger"

♻️ Proposed approach

Import the daemon's logger (or create one) and replace the console.log calls:

+import { logger } from './logger.js'; // or appropriate logger import
 
-    console.log(`[Daemon] Removing stale PID file (pid ${pid} not running)`);
+    logger.info(`Removing stale PID file (pid ${pid} not running)`);

-  console.log(`[Daemon] PID file written: ${PID_FILE} (pid ${process.pid})`);
+  logger.info(`PID file written: ${PID_FILE} (pid ${process.pid})`);

-    console.log('[Daemon] PID file removed');
+    logger.info('PID file removed');

Also applies to: 49-49, 58-58

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

In `@apps/daemon/src/pid.ts` at line 35, Replace the console.log calls in this PID
management module with the application's logger: import the app logger (use the
actual exported name from your logging module, e.g., logger or daemonLogger) at
the top of the file and change the console.log invocations (the "[Daemon]
Removing stale PID file..." and the other console.log occurrences handling PID
file operations) to logger.info or logger.debug as appropriate, preserving the
original messages but routing them through the app logger API.
apps/desktop/src/main/daemon/entry.ts-78-82 (1)

78-82: ⚠️ Potential issue | 🟡 Minor

Missing validation for params.task before calling storage.saveTask.

The handler checks that params exists but doesn't verify params.task is defined. If a caller sends { } as params, storage.saveTask(undefined) would be called.

🛡️ Suggested defensive check
   server.registerMethod('storage.saveTask', (params) => {
-    if (params) {
+    if (params?.task) {
       storage.saveTask(params.task);
     }
   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/main/daemon/entry.ts` around lines 78 - 82, The handler
registered with server.registerMethod('storage.saveTask') currently only checks
params but not params.task; update the handler to verify params.task exists and
is valid before calling storage.saveTask. Specifically, add a guard that returns
early (or responds with a clear error) if params is falsy or params.task is
undefined/null/invalid, and only call storage.saveTask(params.task) when the
check passes; refer to the server.registerMethod callback and the
storage.saveTask call to locate where to add this defensive validation.
apps/desktop/src/main/daemon/entry.ts-84-100 (1)

84-100: ⚠️ Potential issue | 🟡 Minor

Similar validation gaps for other storage methods.

The handlers for updateTaskStatus, updateTaskSummary, and addTaskMessage also check only that params exists, but don't validate the required nested properties (taskId, status, summary, message). This could lead to passing undefined values to storage methods.

🛡️ Suggested validation pattern
   server.registerMethod('storage.updateTaskStatus', (params) => {
-    if (params) {
+    if (params?.taskId && params.status !== undefined) {
       storage.updateTaskStatus(params.taskId, params.status, params.completedAt);
     }
   });

   server.registerMethod('storage.updateTaskSummary', (params) => {
-    if (params) {
+    if (params?.taskId && params.summary !== undefined) {
       storage.updateTaskSummary(params.taskId, params.summary);
     }
   });

   server.registerMethod('storage.addTaskMessage', (params) => {
-    if (params) {
+    if (params?.taskId && params.message) {
       storage.addTaskMessage(params.taskId, params.message);
     }
   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/main/daemon/entry.ts` around lines 84 - 100, The handlers
registered via server.registerMethod for 'storage.updateTaskStatus',
'storage.updateTaskSummary', and 'storage.addTaskMessage' only check that params
is truthy but don't validate required nested fields (taskId, status, summary,
message), which can pass undefined into storage.updateTaskStatus,
storage.updateTaskSummary, and storage.addTaskMessage; update each handler to
explicitly validate the required properties (e.g., ensure params.taskId is
present and params.status/params.summary/params.message as applicable), and on
missing/invalid values either log an error and return early or throw a clear
error so the storage methods are only called with valid inputs.
apps/desktop/src/main/daemon/service-manager.ts-69-72 (1)

69-72: ⚠️ Potential issue | 🟡 Minor

Invalid fallback path '~' won't expand.

The '~' string literal won't be expanded by Node.js path operations. If HOME is undefined, the service file path will be incorrect.

🐛 Proposed fix
 function getSystemdServiceDir(): string {
-  const configDir = process.env.XDG_CONFIG_HOME || path.join(process.env.HOME || '~', '.config');
+  const home = process.env.HOME || require('os').homedir();
+  const configDir = process.env.XDG_CONFIG_HOME || path.join(home, '.config');
   return path.join(configDir, 'systemd', 'user');
 }

Note: Since os isn't imported, you could also use app.getPath('home') which is already available from Electron.

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

In `@apps/desktop/src/main/daemon/service-manager.ts` around lines 69 - 72, The
fallback in getSystemdServiceDir uses the literal '~' which isn't expanded;
replace the fallback with a real home directory resolver (e.g., use os.homedir()
after importing os, or use Electron's app.getPath('home') since Electron is
available) and update the configDir computation to use that value instead of '~'
so path.join produces a valid path; ensure you add the corresponding import (os
or Electron app) and update any references to process.env.HOME accordingly in
getSystemdServiceDir.
packages/agent-core/src/common/types/daemon.ts-33-38 (1)

33-38: ⚠️ Potential issue | 🟡 Minor

Let protocol error responses use id: null.

JsonRpcResponse narrows id to string | number, but this same module defines parse/invalid-request errors that must use null when a request cannot be correlated. The current type makes spec-compliant error envelopes impossible without casts.

Also applies to: 59-71

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

In `@packages/agent-core/src/common/types/daemon.ts` around lines 33 - 38, The
JsonRpc response types currently restrict id to string | number which prevents
creating spec-compliant error envelopes that require id: null; update the
JsonRpcResponse interface to allow id: string | number | null and likewise
adjust any related response/error interfaces defined later (the block around the
other response/error types at lines 59-71) so their id fields accept null as
well, ensuring parse/invalid-request error objects can be constructed without
casts.
🧹 Nitpick comments (10)
packages/agent-core/src/daemon/scheduler.ts (1)

160-167: Timer does not align to minute boundaries on start.

The timer starts immediately with a 60-second interval, but if addScheduledTask is called at 12:00:30, the first tick occurs at 12:01:30, potentially missing a task scheduled for 0 * * * * (every hour at minute 0) if the hour changes. Consider aligning the first tick to the next minute boundary.

♻️ Suggested alignment approach
 function startTimer(): void {
-  // Check every 60 seconds (aligned to minute boundaries)
-  timerId = setInterval(() => {
-    tick();
-  }, 60_000);
+  // Align to next minute boundary, then check every 60 seconds
+  const now = new Date();
+  const msUntilNextMinute = (60 - now.getSeconds()) * 1000 - now.getMilliseconds();
+  
+  setTimeout(() => {
+    tick();
+    timerId = setInterval(() => {
+      tick();
+    }, 60_000);
+  }, msUntilNextMinute);
 
   console.log('[Scheduler] Timer started');
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/agent-core/src/daemon/scheduler.ts` around lines 160 - 167,
startTimer currently calls setInterval immediately so the first tick can be
offset from minute boundaries; change startTimer to compute the milliseconds
until the next minute boundary, use setTimeout to call tick once at that aligned
time, then start the recurring timerId = setInterval(tick, 60_000) from within
that timeout so all subsequent ticks are aligned to minute boundaries; update
any references to timerId/tick accordingly and clear existing timers as before
to avoid duplicates.
apps/daemon/tsconfig.json (1)

20-23: Consider using TypeScript project references instead of including agent-core source directly.

Including ../../packages/agent-core/src/**/* directly in compilation scope may cause duplicate compilation and slower builds. Consider using TypeScript project references or referencing the built package instead.

However, if this is intentional for development (e.g., noEmit: true suggests type-checking only), this approach works.

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

In `@apps/daemon/tsconfig.json` around lines 20 - 23, The tsconfig.json currently
includes the agent-core source via the "include" entry
("../../packages/agent-core/src/**/*"), which can cause duplicate compilation
and slow builds; update the tsconfig for apps/daemon to use TypeScript project
references (add a "references" array pointing to the built agent-core tsconfig)
or reference the built package instead, and remove the direct source include,
while preserving the existing noEmit/type-checking intent (ensure "noEmit"
remains if you still only want type checks); change the include back to only
"src/**/*" and add a proper "references": [{ "path": "../../packages/agent-core"
}] so the compiler uses project references rather than compiling agent-core
source inline.
apps/daemon/src/pid.ts (1)

13-16: Add braces for consistency with coding guidelines.

The try/catch blocks use single-line bodies without explicit braces structure. While technically functional, the coding guidelines require braces for all control flow statements.

♻️ Proposed fix
 function isProcessRunning(pid: number): boolean {
-  try { process.kill(pid, 0); return true; }
-  catch (e) { return (e as NodeJS.ErrnoException).code === 'EPERM'; }
+  try {
+    process.kill(pid, 0);
+    return true;
+  } catch (e) {
+    return (e as NodeJS.ErrnoException).code === 'EPERM';
+  }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/daemon/src/pid.ts` around lines 13 - 16, The isProcessRunning function
uses single-line try/catch bodies without braces; update the function
(isProcessRunning) to use explicit block braces for both the try and catch
clauses so it conforms to the coding guidelines—keep the same logic (call
process.kill(pid, 0) and return true on success; in the catch, cast the error to
NodeJS.ErrnoException and return e.code === 'EPERM') and preserve return values
and types.
apps/daemon/src/cli.ts (1)

5-10: Consider edge case: --socket-path or --data-dir followed by another flag.

If the user passes --socket-path --version, the current logic treats --version as the socket path value. This is because argv[i + 1] only checks for truthiness, not whether it's another flag.

♻️ Proposed fix to detect flag-like values
-    if (argv[i] === '--socket-path' && argv[i + 1]) {
+    if (argv[i] === '--socket-path' && argv[i + 1] && !argv[i + 1].startsWith('--')) {
       result.socketPath = argv[i + 1];
       i++;
-    } else if (argv[i] === '--data-dir' && argv[i + 1]) {
+    } else if (argv[i] === '--data-dir' && argv[i + 1] && !argv[i + 1].startsWith('--')) {
       result.dataDir = argv[i + 1];
       i++;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/daemon/src/cli.ts` around lines 5 - 10, The argument parsing treats the
next token as a value even if it's another flag; update the logic around argv,
result.socketPath and result.dataDir so you only accept argv[i+1] as a value
when it exists and does not start with '-' (or '--'); if argv[i+1] is missing or
looks like a flag, do not assign it (optionally emit an error or keep the
current value) and only increment i when you actually consume a value. Ensure
these checks are applied in the same parsing block that sets result.socketPath
and result.dataDir so you don't accidentally treat flags like '--version' as
values.
apps/daemon/__tests__/unit/health.test.ts (1)

67-81: Fake timer setup may not affect the service's start time.

The service is created in the outer beforeEach (line 11-13) before fake timers are enabled in the nested beforeEach (line 68-70). This means the service captures its start time using real Date.now(), not the fake timer. When vi.advanceTimersByTime(5000) runs, it advances the mocked Date.now() but the service's recorded start time remains the real one, which may cause inconsistent behavior.

Consider creating a fresh HealthService instance inside the uptime describe block after enabling fake timers:

🛠️ Suggested fix
   describe('uptime', () => {
+    let uptimeService: HealthService;
+
     beforeEach(() => {
       vi.useFakeTimers();
+      uptimeService = new HealthService();
     });

     afterEach(() => {
       vi.useRealTimers();
     });

     it('should increase over time', () => {
-      const status1 = service.getStatus();
+      const status1 = uptimeService.getStatus();
       vi.advanceTimersByTime(5000);
-      const status2 = service.getStatus();
+      const status2 = uptimeService.getStatus();
       expect(status2.uptime).toBeGreaterThan(status1.uptime);
     });
   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/daemon/__tests__/unit/health.test.ts` around lines 67 - 81, The test
uses fake timers after the outer service instance is created, so HealthService
captured a real start time; recreate the service after enabling fake timers so
uptime uses mocked time — inside the 'uptime' describe block replace or add a
beforeEach that calls vi.useFakeTimers() and then instantiates a new
HealthService (the same constructor used originally, referenced as service or
HealthService) so subsequent vi.advanceTimersByTime(5000) affects
service.getStatus().uptime; also restore timers in afterEach with
vi.useRealTimers().
apps/desktop/e2e/specs/daemon.spec.ts (1)

48-49: Hardcoded delay may cause flakiness.

The 2-second fixed delay for background processing might be insufficient on slower CI machines or excessive on fast ones. Consider using a polling approach or extending ExecutionPage with a method that waits for task progress indicators.

That said, for simulating "UI hidden while processing," a brief fixed wait may be acceptable if the mock processing time is deterministic.

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

In `@apps/desktop/e2e/specs/daemon.spec.ts` around lines 48 - 49, Replace the
hardcoded 2s sleep in daemon.spec.ts with a deterministic wait: add or use a
helper on ExecutionPage (e.g., waitForProcessingIndicator, waitForDaemonIdle or
waitForTaskCompletion) that polls the DOM or uses
page.waitForSelector/page.waitForFunction to detect when the processing UI is
hidden or the daemon task is complete; update the test to call that method
instead of new Promise/setTimeout so the test waits reliably across CI speeds
and avoids flakiness.
apps/desktop/__tests__/unit/main/daemon/server.unit.test.ts (1)

69-70: Avoid fixed startup sleeps in the socket tests.

These hard-coded 200ms waits make the suite timing-sensitive; slower CI runners can still hit the connect before the server finishes binding. Prefer a readiness signal or a short retry loop that waits until the socket actually accepts connections.

Also applies to: 90-90, 112-112, 152-152, 189-189, 212-212

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

In `@apps/desktop/__tests__/unit/main/daemon/server.unit.test.ts` around lines 69
- 70, Replace the fixed 200ms startup sleeps (the statements like await new
Promise((resolve) => setTimeout(resolve, 200));) with a deterministic readiness
check: either have the server instance emit/resolve a "ready" promise when it
finishes binding (add and await server.once('listening') or a custom ready
promise in the server creation helper), or implement a short retry loop that
attempts to connect to the socket until successful (with a small delay and a
timeout). Update each test that uses the fixed sleep (the occurrences of the
await new Promise(...) sleep) to await the server readiness signal or the
connection-retry loop instead so tests only proceed once the socket actually
accepts connections.
apps/desktop/src/main/daemon/service-manager.ts (1)

110-117: Consider adding a timeout to execSync calls.

If systemctl hangs (e.g., waiting for D-Bus), the Electron main process will block indefinitely. A timeout provides a safety net.

♻️ Proposed fix
   try {
-    execSync('systemctl --user daemon-reload', { stdio: 'pipe' });
-    execSync(`systemctl --user enable ${SYSTEMD_SERVICE_NAME}`, { stdio: 'pipe' });
+    execSync('systemctl --user daemon-reload', { stdio: 'pipe', timeout: 10_000 });
+    execSync(`systemctl --user enable ${SYSTEMD_SERVICE_NAME}`, { stdio: 'pipe', timeout: 10_000 });
     console.log('[ServiceManager] systemd user service enabled');
   } catch (err) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/main/daemon/service-manager.ts` around lines 110 - 117, The
execSync calls in ServiceManager that run 'systemctl --user daemon-reload' and
'systemctl --user enable ${SYSTEMD_SERVICE_NAME}' can hang and block the
Electron main process; add a timeout option to these execSync invocations (e.g.,
{ stdio: 'pipe', timeout: <ms> }) so the calls will throw after a bounded
period, and handle the timeout error in the existing catch block (preserve
logging via console.error and rethrow). Locate the execSync usage in
service-manager.ts around the ServiceManager systemd enable logic and update
both calls to include a sensible timeout value and ensure error handling still
reports the error.
apps/daemon/src/index.ts (1)

55-329: Consider splitting the large main() function into smaller modules.

At ~275 lines, this function handles PID locking, storage init, service creation, RPC registration, event wiring, server startup, and shutdown. Extracting logical sections (e.g., initializeServices(), registerRpcMethods(), setupShutdown()) would improve maintainability.

As per coding guidelines: "New files must be < 200 lines — split into logical modules if needed"

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

In `@apps/daemon/src/index.ts` around lines 55 - 329, The main() function is too
large; split it into smaller helper functions to keep files <200 lines and
improve readability: extract service creation into initializeServices() (handle
StorageService, TaskService, HealthService, PermissionService,
ThoughtStreamService, authToken generation and related paths), move all
rpc.registerMethod(...) calls into registerRpcMethods(rpc, taskService, storage,
permissionService, thoughtStreamService, healthService), pull event wiring into
wireTaskEvents(taskService, rpc, thoughtStreamService, healthService), factor
server startup and env setup into startServers(rpc, permissionService,
thoughtStreamService, storageService, taskService) that returns ports/auth, and
encapsulate shutdown logic into setupShutdown(taskService, thoughtStreamService,
permissionService, rpc, storageService, pidLock). Then refactor main() to call
these helpers in order (acquirePidLock remains in main before
initializeServices) so behavior is unchanged but code is modular.
packages/agent-core/src/daemon/server.ts (1)

99-99: Replace console.error with the app's logger.

Production code should use the application's logging infrastructure rather than direct console calls.

As per coding guidelines: "No console.log in production code — use the app's existing logger"

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

In `@packages/agent-core/src/daemon/server.ts` at line 99, Replace the direct
console.error call in the DaemonServer request handler with the application's
logger: call the module's logger.error (e.g., logger.error or
daemonLogger.error) instead of console.error and pass the same message and the
error object (keep `[DaemonServer] Handler error for ${request.method}` and the
errorMessage as arguments). If no logger is imported in server.ts, import the
shared app logger used elsewhere (e.g., processLogger/logger) and use it here so
production logging goes through the app's logging infrastructure.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 02434466-e45e-4f03-8364-0f5b4f98e134

📥 Commits

Reviewing files that changed from the base of the PR and between eb93a97 and 5a7de55.

📒 Files selected for processing (57)
  • apps/daemon/__tests__/unit/health.test.ts
  • apps/daemon/__tests__/unit/http-server-factory.test.ts
  • apps/daemon/__tests__/unit/rate-limiter.test.ts
  • apps/daemon/package.json
  • apps/daemon/src/cli.ts
  • apps/daemon/src/daemon-options.ts
  • apps/daemon/src/health.ts
  • apps/daemon/src/http-server-factory.ts
  • apps/daemon/src/index.ts
  • apps/daemon/src/mcp-bridges.ts
  • apps/daemon/src/permission-service.ts
  • apps/daemon/src/pid.ts
  • apps/daemon/src/rate-limiter.ts
  • apps/daemon/src/storage-service.ts
  • apps/daemon/src/task-service.ts
  • apps/daemon/src/thought-stream-service.ts
  • apps/daemon/src/websocket.ts
  • apps/daemon/tsconfig.json
  • apps/daemon/tsup.config.ts
  • apps/daemon/vitest.config.ts
  • apps/desktop/__tests__/unit/main/daemon/scheduler.unit.test.ts
  • apps/desktop/__tests__/unit/main/daemon/server.unit.test.ts
  • apps/desktop/e2e/specs/daemon.spec.ts
  • apps/desktop/src/main/daemon-bootstrap.ts
  • apps/desktop/src/main/daemon/cli-bridge.ts
  • apps/desktop/src/main/daemon/entry.ts
  • apps/desktop/src/main/daemon/server.ts
  • apps/desktop/src/main/daemon/service-manager.ts
  • apps/desktop/src/main/index.ts
  • apps/desktop/src/main/ipc/handlers/settings-handlers.ts
  • apps/desktop/src/main/ipc/task-callbacks.ts
  • apps/desktop/src/main/tray.ts
  • apps/desktop/src/preload/index.ts
  • apps/web/locales/en/settings.json
  • apps/web/locales/zh-CN/settings.json
  • apps/web/src/client/components/layout/SettingsDialog.tsx
  • apps/web/src/client/components/settings/DaemonPanel.tsx
  • apps/web/src/client/components/ui/switch.tsx
  • apps/web/src/client/lib/accomplish.ts
  • docs/architecture.md
  • packages/agent-core/src/common/types/daemon.ts
  • packages/agent-core/src/daemon/client.ts
  • packages/agent-core/src/daemon/index.ts
  • packages/agent-core/src/daemon/ipc-transport.ts
  • packages/agent-core/src/daemon/pid-lock.ts
  • packages/agent-core/src/daemon/scheduler.ts
  • packages/agent-core/src/daemon/server.ts
  • packages/agent-core/src/daemon/socket-path.ts
  • packages/agent-core/src/daemon/transport.ts
  • packages/agent-core/src/daemon/types.ts
  • packages/agent-core/src/factories/storage.ts
  • packages/agent-core/src/index.ts
  • packages/agent-core/src/storage/migrations/index.ts
  • packages/agent-core/src/storage/migrations/v013-daemon.ts
  • packages/agent-core/src/storage/repositories/appSettings.ts
  • packages/agent-core/src/storage/repositories/index.ts
  • packages/agent-core/src/types/storage.ts

Comment thread apps/daemon/package.json Outdated
Comment thread apps/daemon/src/websocket.ts Outdated
Comment thread apps/desktop/src/main/daemon-bootstrap.ts Outdated
- websocket.ts: wrap message handler in try/catch with shape validation
- daemon-bootstrap.ts: register missing task.start/session.resume/permission.respond RPCs
- pnpm-lock.yaml: regenerate after adding daemon package
Addresses: 2971335381, 2971335383, 2971335385

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

♻️ Duplicate comments (2)
apps/desktop/src/main/daemon-bootstrap.ts (2)

356-369: ⚠️ Potential issue | 🟠 Major

requestId-based permission replies are still dropped in fallback mode.

This branch logs and returns instead of resolving the outstanding permission request. Any permission flow keyed by requestId will stay blocked whenever the child daemon is unavailable and the app falls back in-process.

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

In `@apps/desktop/src/main/daemon-bootstrap.ts` around lines 356 - 369, The
current permission.respond handler returns early when a response contains
requestId, which drops requestId-keyed flows in fallback mode; instead, when
requestId is present (inside the srv.registerMethod('permission.respond')
handler), look up and resolve the outstanding permission request keyed by
requestId (use your existing pending-request store/resolver — e.g., the map or
function that tracks in-process permission requests) with the provided response,
logging if no pending entry exists; retain the existing
taskManager.hasActiveTask check and only fall back to the original
warning+return if there truly is no pending request to resolve.

297-302: ⚠️ Potential issue | 🟠 Major

Keep storage in sync when cancelling queued tasks.

task.cancelQueued removes the task from the in-memory queue and returns, but unlike task.cancel it never marks the persisted task as cancelled. After this RPC succeeds, history can still show the task as queued.

🐛 Suggested fix
   srv.registerMethod('task.cancelQueued', (params) => {
     if (!params) {
       return false;
     }
-    return taskManager.cancelQueuedTask(params.taskId);
+    const cancelled = taskManager.cancelQueuedTask(params.taskId);
+    if (cancelled) {
+      storage.updateTaskStatus(params.taskId, 'cancelled', new Date().toISOString());
+    }
+    return cancelled;
   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/main/daemon-bootstrap.ts` around lines 297 - 302, The
handler for 'task.cancelQueued' only removes the task from the in-memory queue
(taskManager.cancelQueuedTask) but does not update the persisted task record;
change the handler so that after taskManager.cancelQueuedTask(params.taskId)
succeeds you also update the persisted task state to "cancelled" (e.g. call the
same persistence code used by task.cancel—such as
taskManager.markTaskCancelled(params.taskId) or
taskStore.updateTaskStatus(params.taskId, 'cancelled')), handle and log any
persistence errors, and return a combined success/failure result so history no
longer shows the task as queued.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/desktop/src/main/daemon-bootstrap.ts`:
- Around line 330-337: The handlers call taskManager.startTask(...) before
persisting, which can leave orphaned running tasks if storage.saveTask(...)
later fails; change the flow to create a task record and persist it first, then
start the background work. Specifically, for the 'task.start' and the other
handler (the one at lines 340-351), construct the taskId (using createTaskId()
if needed), build any necessary callbacks (buildInProcessCallbacks(taskId, srv,
storage)) only if they don't require a started task, create the task
metadata/object and call storage.saveTask(task) first, then call
taskManager.startTask(taskId, config, callbacks) and update/save any runtime
fields after start; ensure error handling rolls back or cleans up if startTask
fails. Use the existing symbols taskManager.startTask, storage.saveTask,
createTaskId, buildInProcessCallbacks and srv.registerMethod to locate and
update the handlers.
- Line 58: Replace direct console.log calls that print lifecycle messages (e.g.,
"['DaemonBootstrap'] Running in child-process mode" and the other instances at
the noted positions) with the app's desktop logger so logs go through the
standard formatting/sinks; locate the console.log usages in daemon-bootstrap
(search for the exact message strings) and swap them to use the existing logger
instance (e.g., logger.info(...) or desktopLogger.info(...)), preserving the
original messages and log level, and ensure the logger is imported or referenced
consistently where those console.log calls currently appear.
- Around line 1-444: The file is too large and mixes unrelated responsibilities;
split bootstrapDaemon into smaller modules: extract child-process logic
(spawnDaemonProcess and getDaemonEntryPath + related constants and daemonProcess
state and stdout/stderr forwarding) into a daemon-spawn module, extract
in-process wiring (bootstrapInProcess, registerInProcessHandlers,
buildInProcessCallbacks and any references to DaemonServer/DaemonClient
creation) into an in-process module, and extract lifecycle helpers
(shutdownDaemon, getDaemonClient/getDaemonServer/getDaemonMode and shared state
like client/server/mode) into a small lifecycle module; update the top-level
daemon-bootstrap to orchestrate these modules (importing spawn, in-process
bootstrap, and lifecycle functions) so each new file stays under 200 lines and
preserve function names spawnDaemonProcess, getDaemonEntryPath,
bootstrapInProcess, registerInProcessHandlers, buildInProcessCallbacks,
shutdownDaemon, getDaemonClient, getDaemonServer, and getDaemonMode for
compatibility.
- Around line 95-101: The startup promise must reject immediately if the child
process exits before sending the 'daemon:ready' event; update the exit/close
handler that currently only clears daemonProcess (around where daemonProcess,
timer, and DAEMON_READY_TIMEOUT_MS are used) to call reject(new Error(...))
right away when not yet ready, clear the readiness timeout
(clearTimeout(timer)), and ensure daemonProcess is killed and nulled so the
promise does not hang until DAEMON_READY_TIMEOUT_MS; keep the existing
resolution path on 'daemon:ready' unchanged.

---

Duplicate comments:
In `@apps/desktop/src/main/daemon-bootstrap.ts`:
- Around line 356-369: The current permission.respond handler returns early when
a response contains requestId, which drops requestId-keyed flows in fallback
mode; instead, when requestId is present (inside the
srv.registerMethod('permission.respond') handler), look up and resolve the
outstanding permission request keyed by requestId (use your existing
pending-request store/resolver — e.g., the map or function that tracks
in-process permission requests) with the provided response, logging if no
pending entry exists; retain the existing taskManager.hasActiveTask check and
only fall back to the original warning+return if there truly is no pending
request to resolve.
- Around line 297-302: The handler for 'task.cancelQueued' only removes the task
from the in-memory queue (taskManager.cancelQueuedTask) but does not update the
persisted task record; change the handler so that after
taskManager.cancelQueuedTask(params.taskId) succeeds you also update the
persisted task state to "cancelled" (e.g. call the same persistence code used by
task.cancel—such as taskManager.markTaskCancelled(params.taskId) or
taskStore.updateTaskStatus(params.taskId, 'cancelled')), handle and log any
persistence errors, and return a combined success/failure result so history no
longer shows the task as queued.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 87a07586-44d0-420f-ad8d-8d6b5fd73fea

📥 Commits

Reviewing files that changed from the base of the PR and between 5a7de55 and 1a83d74.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (2)
  • apps/daemon/src/websocket.ts
  • apps/desktop/src/main/daemon-bootstrap.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/daemon/src/websocket.ts

Comment thread apps/desktop/src/main/daemon-bootstrap.ts Outdated
Comment thread apps/desktop/src/main/daemon-bootstrap.ts Outdated
Comment thread apps/desktop/src/main/daemon-bootstrap.ts Outdated
Comment thread apps/desktop/src/main/daemon-bootstrap.ts Outdated
Avishay Mashiach added 8 commits March 22, 2026 18:50
- daemon-bootstrap.ts: replace console.log/warn/error with getLogCollector().logEnv()
- daemon-bootstrap.ts: reject promise immediately on pre-ready child process exit
- daemon-bootstrap.ts: persist task to storage before calling startTask (task.start + session.resume)
Addresses: 2971423605, 2971423607, 2971423609
Skipped  : 2971423602
…ck handlers, ESM fix, error handling

- scheduler.ts: replace console.log with structured logger
- switch.tsx: rewrite using @radix-ui/react-switch with CVA variants
- vitest.config.ts: fix __dirname in ESM context using fileURLToPath
- daemon-bootstrap.ts: task.cancelQueued now persists cancelled status to storage
- daemon-bootstrap.ts: permission.respond now resolves requestId-based permissions
  via in-process permission handler (resolvePermission/resolveQuestion)
- daemon/types: add task.stop, task.status, health.check to DaemonMethodMap
- daemon/types: add task.thought, task.checkpoint, task.error to DaemonNotificationMap
- daemon/types: add HealthCheckResult interface
- config-generator.ts: add authToken field to ConfigGeneratorOptions
- package.json: add ws and @types/ws dependencies to daemon

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

Caution

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

⚠️ Outside diff range comments (1)
packages/agent-core/src/index.ts (1)

1-3: ⚠️ Potential issue | 🔴 Critical

Remove leftover debug statement.

Line 2 contains a console.log that appears to be a sync/test artifact. This violates the coding guideline prohibiting console.log in production code.

🐛 Proposed fix
 // =============================================================================
-console.log('[agent-core] u2d sync test');
 // `@accomplish/core` - Public API (v0.4.0)

As per coding guidelines: "No console.log in production code — use the app's existing logger".

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

In `@packages/agent-core/src/index.ts` around lines 1 - 3, Remove the leftover
debug statement console.log('[agent-core] u2d sync test') from the top-level
module; either delete the line or replace it with the project's logger (e.g.,
call the existing logger.info/debug method used across the app) so no raw
console.log remains in packages/agent-core/src/index.ts and ensure any import
needed for the logger (the app logger or processLogger) is added if you choose
to log instead of removing.
🧹 Nitpick comments (5)
apps/web/src/client/components/settings/DaemonPanel.tsx (1)

78-87: Use the shared UI Button instead of a raw <button>.

This keeps behavior/theming consistent with the rest of settings UI and avoids duplicating button styling.

As per coding guidelines apps/web/src/client/components/**/*.{ts,tsx}: "Reuse UI components — check apps/web/src/client/components/ui/ before creating new ones".

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

In `@apps/web/src/client/components/settings/DaemonPanel.tsx` around lines 78 -
87, Replace the raw <button> in DaemonPanel with the shared UI Button component:
import and use Button (the shared component named Button) instead of the HTML
button, move the onClick handler (void
navigator.clipboard.writeText(socketPath).catch(() => {})) and title prop onto
Button, and remove the duplicated className styling so theming/behavior come
from the shared Button; ensure the Button usage preserves the same text ("Copy")
and accessibility attributes.
packages/agent-core/src/daemon/rpc-server.ts (1)

205-212: Consider using a static import for node:fs/promises.

The dynamic import is functional but unnecessary since this module always needs unlink. A static import at the top of the file would be cleaner and more consistent.

♻️ Suggested refactor
 import { createServer, type Server, type Socket } from 'node:net';
 import { randomUUID } from 'node:crypto';
+import { unlink } from 'node:fs/promises';
 import type { JsonRpcMessage, JsonRpcRequest, JsonRpcResponse } from '../common/types/daemon.js';

Then simplify the method:

   private async removeStaleSocket(): Promise<void> {
-    const { unlink } = await import('node:fs/promises');
     try {
       await unlink(this.socketPath);
     } catch {
       // File doesn't exist — that's fine
     }
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/agent-core/src/daemon/rpc-server.ts` around lines 205 - 212, The
method removeStaleSocket currently does a dynamic import of 'node:fs/promises'
for unlink; replace that with a static import (import { unlink } from
'node:fs/promises') at the top of the file and remove the dynamic import inside
removeStaleSocket so the method simply calls await unlink(this.socketPath)
inside the existing try/catch (catching and ignoring ENOENT/non‑existent file
cases) — update references to unlink and keep socketPath usage intact.
apps/daemon/src/index.ts (2)

70-90: Consider using the daemon logger instead of direct console calls.

The daemon subsystem includes a logger at packages/agent-core/src/daemon/logger.ts. Using it here would provide consistent formatting and the ability to control debug output via DEBUG environment variable.

♻️ Suggested approach
 import {
   DaemonRpcServer,
   getSocketPath,
   acquirePidLock,
   installCrashHandlers,
+  logger,
   type PidLockHandle,
   ...
 } from '@accomplish_ai/agent-core';

-  console.log('[Daemon] Starting...');
+  logger.info('Starting...');

-  console.log(`[Daemon] PID lock acquired: ${pidLock.pidPath} (pid=${process.pid})`);
+  logger.info(`PID lock acquired: ${pidLock.pidPath} (pid=${process.pid})`);

-  console.warn(`[Daemon] Crash recovery: marking stale task ${task.id} as failed`);
+  logger.warn(`Crash recovery: marking stale task ${task.id} as failed`);

Apply similar changes throughout the file.

As per coding guidelines: "No console.log in production code — use the app's existing logger"

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

In `@apps/daemon/src/index.ts` around lines 70 - 90, Replace direct console calls
in this startup sequence with the daemon logger: import and use the logger
exported by the daemon logger module and call its methods instead of
console.log/console.warn (e.g., use logger.info(`[Daemon] Starting...`),
logger.info when reporting PID lock from acquirePidLock() and logger.debug or
info for authToken generation, and logger.warn for crash-recovery messages).
Update the block that uses pidLock/acquirePidLock, the authToken generation, and
the StorageService flow (storage.initialize, storage.getTasks,
storage.updateTaskStatus) to log via the daemon logger consistently and remove
console.* usage throughout the file.

1-373: Consider splitting this file for maintainability.

At 373 lines, this file exceeds the 200-line guideline significantly. While it's a cohesive entrypoint, consider extracting:

  • RPC method registration (lines 129-242) into a separate rpc-methods.ts module
  • Event forwarding setup (lines 244-273) into an event-handlers.ts module

This would make the main function easier to follow and test individual components.

As per coding guidelines: "New files must be < 200 lines — split into logical modules if needed"

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

In `@apps/daemon/src/index.ts` around lines 1 - 373, This file is too large;
extract the RPC registration block around rpc.registerMethod(...) (uses
safeHandler, taskStartSchema, taskIdSchema, permissionResponseSchema,
resumeSessionSchema, validate, taskService, storage, permissionService,
thoughtStreamService, healthService) into a new module (e.g., rpc-methods.ts)
that exports a function like registerRpcMethods(rpc, taskService, storage,
permissionService, thoughtStreamService, healthService) which wires up all
rpc.registerMethod calls and returns any teardown if needed; likewise extract
the event forwarding block that calls taskService.on(...) and
thoughtStreamService.unregisterTask/registerTask into a new module (e.g.,
event-handlers.ts) that exports setupEventHandlers(taskService,
thoughtStreamService, rpc, healthService) and attaches those listeners. Update
main() to call the two new functions and ensure imports for safeHandler and
schemas are moved into rpc-methods.ts so the top-level file stays under 200
lines.
apps/daemon/src/thought-stream-service.ts (1)

15-31: Consider if empty strings should be allowed for content and summary.

The taskId field requires .min(1) but content (line 17) and summary (line 26) accept empty strings. If empty content/summary is invalid, add .min(1) for consistency.

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

In `@apps/daemon/src/thought-stream-service.ts` around lines 15 - 31, The schemas
allow empty strings for content and summary while taskId enforces non-empty;
update thoughtEventSchema and checkpointEventSchema to require non-empty values
by adding .min(1) to the content property in thoughtEventSchema and to the
summary property in checkpointEventSchema (i.e., change content: z.string() ->
z.string().min(1) and summary: z.string() -> z.string().min(1)) so they
consistently reject empty strings; ensure no other logic relies on empty
content/summary before committing.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/desktop/src/main/daemon/server.ts`:
- Around line 1-231: The file is over the repo's 200-line limit for new TS
files; split it into smaller modules by extracting logical pieces such as socket
path and utilities (getSocketPath, isNotification), JSON-RPC request/response
types and handleLine (and MethodHandler map), and server lifecycle
(startDaemonServer, stopDaemonServer) into separate files; update
imports/exports so startDaemonServer and stopDaemonServer remain public from the
original module, move method registration (registerMethod and methodHandlers)
alongside handleLine into a handlers module, and keep net/fs/electron usage
localized to a daemon-server module that composes the pieces and performs
listen/cleanup.
- Line 203: Replace the direct console.log calls with the app's structured
logger: import or use the existing logger instance (e.g., logger or appLogger)
and call logger.info(...) instead of console.log for the socket server messages
— specifically replace console.log(`[Daemon] Socket server listening at
${socketPath}`) and the similar occurrence around line 231 with
logger.info(`[Daemon] Socket server listening at ${socketPath}`) (or an
equivalent structured log call), ensuring no console.log calls remain in this
file.
- Around line 167-179: The newline-framing buffer in the socket data handler can
grow unbounded; add a maximum allowed buffer length (e.g., MAX_BUFFER_SIZE) and
enforce it inside the socket.on('data') callback that accumulates into buffer:
after appending data.toString(), check buffer.length and if it exceeds
MAX_BUFFER_SIZE either drop/ destroy the socket (socket.destroy()/socket.end())
and log an error or truncate the buffer and continue, then proceed to split on
'\n' and call handleLine(trimmed, socket) as before; add the constant
(MAX_BUFFER_SIZE) near the top of this module and ensure any early termination
is graceful and logged.
- Around line 89-102: The parsed object check is too loose and malformed
JSON-RPC objects (e.g., missing jsonrpc, non-string method, invalid id types)
fall through to "Method not found"; update the validation after parsing to
assert request.jsonrpc === '2.0', typeof request.method === 'string' (unless
it's a notification), and that request.id (when present) is a string, number, or
null; reuse the existing JsonRpcResponse shape and socket.write to return the
same Invalid Request error when these validations fail, and keep using
isNotification(request) to decide whether method may be omitted for
notifications.

In `@apps/web/src/client/components/settings/DaemonPanel.tsx`:
- Around line 11-20: The current useEffect in DaemonPanel swallows errors from
accomplish.getRunInBackground() and accomplish.getDaemonSocketPath() by using
.catch(() => {}); update it to handle errors: replace the empty catches with
calls that log the error (using the app logger used elsewhere in the app) and
optionally set a local error state to show a small hint in the panel; target the
useEffect block and the promises from accomplish.getRunInBackground and
accomplish.getDaemonSocketPath and ensure you still call
setRunInBackground/setSocketPath on success but on failure log the error
(including the error object) and set an error flag/state that DaemonPanel can
render.
- Around line 22-31: The handler handleToggle currently uses try/finally without
catching errors, so if accomplish.setRunInBackground(next) rejects the promise
bubbles and the user gets no feedback; update handleToggle to catch errors from
accomplish.setRunInBackground(next) (e.g., add a catch block or use try/catch)
and on failure revert any optimistic UI changes (do not call
setRunInBackground(next) or reset it), setSaving(false) in finally, and surface
an error to the user (via existing error state, a toast, or an onError
callback). Reference: handleToggle, accomplish.setRunInBackground,
setRunInBackground, setSaving.

In `@apps/web/src/client/components/ui/switch.tsx`:
- Around line 37-55: The custom onChange prop in SwitchProps and the Switch
component drops Radix's boolean payload and conflicts with Radix's
onCheckedChange; remove the onChange?: () => void declaration from SwitchProps,
stop destructuring onChange in the Switch component, and instead accept/use
Radix's onCheckedChange: (checked: boolean) => void (or simply rely on the
inherited prop) and pass it directly to SwitchPrimitive.Root as
onCheckedChange={onCheckedChange}; keep existing ariaLabel mapping and other
props unchanged and ensure the forwarded ref and switchVariants usage remain the
same.

In `@packages/agent-core/src/daemon/types.ts`:
- Around line 120-133: Remove the unused and divergent RPC type definitions by
deleting the RpcMethodMap, RpcMethod, RpcNotificationMap, and
RpcNotificationType declarations from the daemon/types.ts module; ensure any
references in that file to those symbols are removed or replaced with the
canonical DaemonMethodMap/DaemonNotification types (from common/types/daemon.ts)
and export nothing else that reintroduces the conflicting types so the codebase
uses DaemonMethodMap for RPC method typing consistently.

---

Outside diff comments:
In `@packages/agent-core/src/index.ts`:
- Around line 1-3: Remove the leftover debug statement console.log('[agent-core]
u2d sync test') from the top-level module; either delete the line or replace it
with the project's logger (e.g., call the existing logger.info/debug method used
across the app) so no raw console.log remains in
packages/agent-core/src/index.ts and ensure any import needed for the logger
(the app logger or processLogger) is added if you choose to log instead of
removing.

---

Nitpick comments:
In `@apps/daemon/src/index.ts`:
- Around line 70-90: Replace direct console calls in this startup sequence with
the daemon logger: import and use the logger exported by the daemon logger
module and call its methods instead of console.log/console.warn (e.g., use
logger.info(`[Daemon] Starting...`), logger.info when reporting PID lock from
acquirePidLock() and logger.debug or info for authToken generation, and
logger.warn for crash-recovery messages). Update the block that uses
pidLock/acquirePidLock, the authToken generation, and the StorageService flow
(storage.initialize, storage.getTasks, storage.updateTaskStatus) to log via the
daemon logger consistently and remove console.* usage throughout the file.
- Around line 1-373: This file is too large; extract the RPC registration block
around rpc.registerMethod(...) (uses safeHandler, taskStartSchema, taskIdSchema,
permissionResponseSchema, resumeSessionSchema, validate, taskService, storage,
permissionService, thoughtStreamService, healthService) into a new module (e.g.,
rpc-methods.ts) that exports a function like registerRpcMethods(rpc,
taskService, storage, permissionService, thoughtStreamService, healthService)
which wires up all rpc.registerMethod calls and returns any teardown if needed;
likewise extract the event forwarding block that calls taskService.on(...) and
thoughtStreamService.unregisterTask/registerTask into a new module (e.g.,
event-handlers.ts) that exports setupEventHandlers(taskService,
thoughtStreamService, rpc, healthService) and attaches those listeners. Update
main() to call the two new functions and ensure imports for safeHandler and
schemas are moved into rpc-methods.ts so the top-level file stays under 200
lines.

In `@apps/daemon/src/thought-stream-service.ts`:
- Around line 15-31: The schemas allow empty strings for content and summary
while taskId enforces non-empty; update thoughtEventSchema and
checkpointEventSchema to require non-empty values by adding .min(1) to the
content property in thoughtEventSchema and to the summary property in
checkpointEventSchema (i.e., change content: z.string() -> z.string().min(1) and
summary: z.string() -> z.string().min(1)) so they consistently reject empty
strings; ensure no other logic relies on empty content/summary before
committing.

In `@apps/web/src/client/components/settings/DaemonPanel.tsx`:
- Around line 78-87: Replace the raw <button> in DaemonPanel with the shared UI
Button component: import and use Button (the shared component named Button)
instead of the HTML button, move the onClick handler (void
navigator.clipboard.writeText(socketPath).catch(() => {})) and title prop onto
Button, and remove the duplicated className styling so theming/behavior come
from the shared Button; ensure the Button usage preserves the same text ("Copy")
and accessibility attributes.

In `@packages/agent-core/src/daemon/rpc-server.ts`:
- Around line 205-212: The method removeStaleSocket currently does a dynamic
import of 'node:fs/promises' for unlink; replace that with a static import
(import { unlink } from 'node:fs/promises') at the top of the file and remove
the dynamic import inside removeStaleSocket so the method simply calls await
unlink(this.socketPath) inside the existing try/catch (catching and ignoring
ENOENT/non‑existent file cases) — update references to unlink and keep
socketPath usage intact.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: edb48a4f-1b2a-4da6-9d9c-1c8cd54772ad

📥 Commits

Reviewing files that changed from the base of the PR and between fcac330 and 6a37d16.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (48)
  • apps/daemon/__tests__/unit/health.test.ts
  • apps/daemon/__tests__/unit/http-server-factory.test.ts
  • apps/daemon/__tests__/unit/rate-limiter.test.ts
  • apps/daemon/package.json
  • apps/daemon/src/cli.ts
  • apps/daemon/src/daemon-options.ts
  • apps/daemon/src/health.ts
  • apps/daemon/src/http-server-factory.ts
  • apps/daemon/src/index.ts
  • apps/daemon/src/mcp-bridges.ts
  • apps/daemon/src/permission-service.ts
  • apps/daemon/src/pid.ts
  • apps/daemon/src/rate-limiter.ts
  • apps/daemon/src/storage-service.ts
  • apps/daemon/src/task-service.ts
  • apps/daemon/src/thought-stream-service.ts
  • apps/daemon/tsconfig.json
  • apps/daemon/tsup.config.ts
  • apps/daemon/vitest.config.ts
  • apps/desktop/__tests__/unit/main/daemon/scheduler.unit.test.ts
  • apps/desktop/__tests__/unit/main/daemon/server.unit.test.ts
  • apps/desktop/e2e/specs/daemon.spec.ts
  • apps/desktop/src/main/daemon-bootstrap.ts
  • apps/desktop/src/main/daemon/cli-bridge.ts
  • apps/desktop/src/main/daemon/entry.ts
  • apps/desktop/src/main/daemon/server.ts
  • apps/desktop/src/main/daemon/service-manager.ts
  • apps/desktop/src/preload/index.ts
  • apps/web/package.json
  • apps/web/src/client/components/settings/DaemonPanel.tsx
  • apps/web/src/client/components/ui/switch.tsx
  • docs/architecture.md
  • packages/agent-core/src/common/types/daemon.ts
  • packages/agent-core/src/daemon/client.ts
  • packages/agent-core/src/daemon/crash-handlers.ts
  • packages/agent-core/src/daemon/index.ts
  • packages/agent-core/src/daemon/ipc-transport.ts
  • packages/agent-core/src/daemon/logger.ts
  • packages/agent-core/src/daemon/pid-lock.ts
  • packages/agent-core/src/daemon/rpc-server.ts
  • packages/agent-core/src/daemon/scheduler.ts
  • packages/agent-core/src/daemon/server.ts
  • packages/agent-core/src/daemon/socket-path.ts
  • packages/agent-core/src/daemon/transport.ts
  • packages/agent-core/src/daemon/types.ts
  • packages/agent-core/src/index.ts
  • packages/agent-core/src/opencode/config-generator.ts
  • packages/agent-core/src/storage/migrations/v013-daemon.ts
✅ Files skipped from review due to trivial changes (17)
  • apps/web/package.json
  • packages/agent-core/src/opencode/config-generator.ts
  • apps/daemon/vitest.config.ts
  • apps/daemon/tsup.config.ts
  • apps/daemon/src/health.ts
  • apps/daemon/tsconfig.json
  • apps/daemon/tests/unit/health.test.ts
  • apps/daemon/package.json
  • apps/daemon/tests/unit/http-server-factory.test.ts
  • apps/daemon/src/rate-limiter.ts
  • apps/desktop/tests/unit/main/daemon/server.unit.test.ts
  • apps/desktop/tests/unit/main/daemon/scheduler.unit.test.ts
  • packages/agent-core/src/daemon/server.ts
  • packages/agent-core/src/daemon/pid-lock.ts
  • packages/agent-core/src/common/types/daemon.ts
  • apps/daemon/src/task-service.ts
  • apps/daemon/src/http-server-factory.ts
🚧 Files skipped from review as they are similar to previous changes (19)
  • apps/desktop/src/preload/index.ts
  • packages/agent-core/src/storage/migrations/v013-daemon.ts
  • apps/daemon/tests/unit/rate-limiter.test.ts
  • apps/desktop/src/main/daemon/entry.ts
  • packages/agent-core/src/daemon/socket-path.ts
  • packages/agent-core/src/daemon/transport.ts
  • apps/daemon/src/pid.ts
  • apps/daemon/src/storage-service.ts
  • apps/desktop/e2e/specs/daemon.spec.ts
  • packages/agent-core/src/daemon/ipc-transport.ts
  • docs/architecture.md
  • apps/desktop/src/main/daemon/service-manager.ts
  • apps/daemon/src/cli.ts
  • apps/desktop/src/main/daemon/cli-bridge.ts
  • apps/daemon/src/daemon-options.ts
  • apps/daemon/src/mcp-bridges.ts
  • apps/desktop/src/main/daemon-bootstrap.ts
  • packages/agent-core/src/daemon/index.ts
  • packages/agent-core/src/daemon/scheduler.ts

Comment thread apps/desktop/src/main/daemon/server.ts Outdated
Comment thread apps/desktop/src/main/daemon/server.ts Outdated
Comment thread apps/desktop/src/main/daemon/server.ts
Comment thread apps/desktop/src/main/daemon/server.ts Outdated
Comment thread apps/web/src/client/components/settings/DaemonPanel.tsx
Comment thread apps/web/src/client/components/settings/DaemonPanel.tsx
Comment thread apps/web/src/client/components/ui/switch.tsx
Comment thread packages/agent-core/src/daemon/types.ts Outdated
Avishay Maor added 5 commits March 23, 2026 10:17
- Extract RPC dispatch logic from server.ts to rpc-dispatcher.ts to keep
  server.ts under 200 lines and focused on socket lifecycle (fix #2972626290)
- Add isValidJsonRpcRequest type guard returning -32600 Invalid Request for
  malformed JSON-RPC envelopes (fix #2972626292)
- Add MAX_SOCKET_BUFFER_BYTES (1MB) guard with socket.destroy on overflow
  (fix #2972626296)
- Replace all console.log/warn/error with getLogCollector() app logger in
  server.ts and rpc-dispatcher.ts (fix #2972626299)
- Log instead of silently swallow daemon init errors in DaemonPanel
  (fix #2972626303)
- Add explicit catch block in handleToggle to log save failures and keep
  local state unchanged on error (fix #2972626307)
- Rename Switch onChange prop to onCheckedChange to align with Radix
  Switch.Root API, update DaemonPanel caller (fix #2972626318)
- Consolidate RpcMethodMap/DaemonMethodMap by aliasing RpcMethodMap to
  DaemonMethodMap to prevent type divergence (fix #2972626325)
@mavishay

Copy link
Copy Markdown
Contributor Author

@coderabbitai resume

@coderabbitai

coderabbitai Bot commented Mar 23, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Reviews resumed.

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

Caution

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

⚠️ Outside diff range comments (1)
packages/agent-core/src/index.ts (1)

2-2: ⚠️ Potential issue | 🟡 Minor

Remove debug console.log from production code.

Line 2 contains a debug statement that should not be in production code.

-console.log('[agent-core] u2d sync test');

As per coding guidelines **/{apps,packages}/**/*.{js,ts,tsx}: No console.log in production code — use the app's existing logger.

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

In `@packages/agent-core/src/index.ts` at line 2, Remove the debug
console.log('[agent-core] u2d sync test') from packages/agent-core/src/index.ts
and replace it with the project's structured logger: import and use the existing
logger instance (e.g., logger.debug or processLogger.debug) instead of
console.log, or simply delete the statement if no logging is required; ensure
you add the appropriate import for the logger and no console.log calls remain in
this module.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@packages/agent-core/src/index.ts`:
- Line 2: Remove the debug console.log('[agent-core] u2d sync test') from
packages/agent-core/src/index.ts and replace it with the project's structured
logger: import and use the existing logger instance (e.g., logger.debug or
processLogger.debug) instead of console.log, or simply delete the statement if
no logging is required; ensure you add the appropriate import for the logger and
no console.log calls remain in this module.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2dbe7e71-f5ca-458b-aec5-e229ecc2539f

📥 Commits

Reviewing files that changed from the base of the PR and between 6a37d16 and 12ed8e1.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (15)
  • apps/desktop/__tests__/unit/main/daemon/server.unit.test.ts
  • apps/desktop/e2e/docker/Dockerfile
  • apps/desktop/src/main/daemon/rpc-dispatcher.ts
  • apps/desktop/src/main/daemon/server.ts
  • apps/desktop/src/main/index.ts
  • apps/desktop/src/main/ipc/task-callbacks.ts
  • apps/desktop/src/preload/index.ts
  • apps/web/locales/en/settings.json
  • apps/web/locales/zh-CN/settings.json
  • apps/web/package.json
  • apps/web/src/client/components/settings/DaemonPanel.tsx
  • apps/web/src/client/components/ui/switch.tsx
  • apps/web/src/client/lib/accomplish.ts
  • packages/agent-core/src/daemon/types.ts
  • packages/agent-core/src/index.ts
✅ Files skipped from review due to trivial changes (5)
  • apps/web/locales/en/settings.json
  • apps/desktop/e2e/docker/Dockerfile
  • apps/web/package.json
  • apps/web/src/client/components/settings/DaemonPanel.tsx
  • apps/web/locales/zh-CN/settings.json
🚧 Files skipped from review as they are similar to previous changes (4)
  • apps/web/src/client/lib/accomplish.ts
  • apps/web/src/client/components/ui/switch.tsx
  • packages/agent-core/src/daemon/types.ts
  • apps/desktop/src/main/ipc/task-callbacks.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: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/desktop/src/main/daemon/rpc-dispatcher.ts`:
- Around line 132-142: The RPC error path logs the full exception via safeLog
but then includes the raw error string in the JsonRpcResponse (constructed as
JsonRpcResponse with jsonrpc/id/error) which can leak internal details; change
the response to return a sanitized generic message (e.g., "Internal error") and
optionally a non-sensitive metadata field such as errorType or a correlation id
instead of err.message, while keeping the full err message only in safeLog;
update the code around safeLog(...) and the JsonRpcResponse construction in
rpc-dispatcher.ts (look for variables method, id, err and the JsonRpcResponse
object) to remove err.message/String(err) from the response payload and replace
it with safe metadata.
- Around line 13-26: The safeLog function currently falls back to console.* and
uses braceless single-line branches; remove any direct console.* usage in
production by replacing the console fallback with the app logger (use
getLogCollector() return value or a no-op logger) so all logs always go through
logger.log (or are dropped), and add braces to every if/else block in this file
(including the branches inside safeLog and the other braceless blocks around
lines referenced 55–60) to satisfy the curly rule; refer to the safeLog function
and any other braceless conditional blocks in rpc-dispatcher.ts when making the
changes.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 66e0c953-ba68-46d9-8fa7-a22e2e20513c

📥 Commits

Reviewing files that changed from the base of the PR and between 3a63795 and 9c26cca.

📒 Files selected for processing (2)
  • apps/desktop/src/main/daemon/rpc-dispatcher.ts
  • apps/desktop/src/main/daemon/server.ts
✅ Files skipped from review due to trivial changes (1)
  • apps/desktop/src/main/daemon/server.ts

Comment thread apps/desktop/src/main/daemon/rpc-dispatcher.ts
Comment thread apps/desktop/src/main/daemon/rpc-dispatcher.ts
@mavishay

Copy link
Copy Markdown
Contributor Author

@coderabbitai resume

@coderabbitai

coderabbitai Bot commented Mar 23, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Reviews resumed.

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

♻️ Duplicate comments (2)
apps/desktop/src/main/daemon/daemon-inprocess-handlers.ts (2)

128-139: ⚠️ Potential issue | 🟠 Major

Don’t mark resumed tasks as failed on post-start persistence errors.

session.resume still wraps startTask and storage writes in the same try/catch, so a storage failure after successful start can incorrectly set status to failed.

Suggested fix
-  try {
-    const task = await taskManager.startTask(taskId, { prompt, sessionId }, callbacks);
-    if (existingTaskId) {
-      storage.updateTaskStatus(existingTaskId, task.status, new Date().toISOString());
-    } else {
-      storage.saveTask(task);
-    }
-    return task;
-  } catch (err) {
-    storage.updateTaskStatus(taskId, 'failed', new Date().toISOString());
-    throw err;
-  }
+  let task;
+  try {
+    task = await taskManager.startTask(taskId, { prompt, sessionId }, callbacks);
+  } catch (err) {
+    storage.updateTaskStatus(taskId, 'failed', new Date().toISOString());
+    throw err;
+  }
+
+  try {
+    if (existingTaskId) {
+      storage.updateTaskStatus(existingTaskId, task.status, new Date().toISOString());
+    } else {
+      storage.saveTask(task);
+    }
+  } catch {
+    // post-start persistence failure — task is running, don't mark as failed
+  }
+  return task;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/main/daemon/daemon-inprocess-handlers.ts` around lines 128 -
139, The current try/catch wraps taskManager.startTask and the subsequent
storage writes so a storage write error after a successful start can mark a
resumed task as 'failed'; change the flow in daemon-inprocess-handlers.ts so you
first await taskManager.startTask(taskId, { prompt, sessionId }, callbacks)
inside its own try/catch and only set status to 'failed' there if startTask
throws, then perform storage.saveTask(task) or
storage.updateTaskStatus(existingTaskId, ...) in a separate try/catch that
handles/publishes persistence errors without mutating the task status for
resumed tasks (i.e., do not call storage.updateTaskStatus(taskId, 'failed', ...)
on storage write failures); refer to taskManager.startTask, storage.saveTask,
storage.updateTaskStatus, and existingTaskId/session.resume to locate and
implement this change.

33-35: ⚠️ Potential issue | 🟠 Major

Wrap the remaining single-line conditionals in braces.

There are still multiple braceless if statements in this file, which will keep failing the enforced curly lint rule.

As per coding guidelines "**/*.{ts,tsx,js,jsx}: Always use braces for if/else/for/while statements — no single-line braceless statements (enforced by curly ESLint rule)".

Also applies to: 56-59, 75-93, 150-152

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

In `@apps/desktop/src/main/daemon/daemon-inprocess-handlers.ts` around lines 33 -
35, Several single-line braceless conditionals remain (e.g., in the
srv.registerMethod('task.delete', (params) => { if (params)
storage.deleteTask(params.taskId); }); handler) and must be changed to use
braces to satisfy the `curly` lint rule; update each braceless conditional
(including similar patterns around the other registerMethod handlers referenced)
to a block form like: if (params) { storage.deleteTask(params.taskId); } (apply
the same change to all other single-line if/else statements in this file such as
the handlers covering the other mentioned ranges).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/desktop/__tests__/unit/main/daemon/helpers/server.helpers.ts`:
- Around line 15-32: Update sendJsonRpc and sendRawLine to robustly manage the
socket lifecycle and guard JSON parsing: add handlers for the socket 'close'
event (in addition to 'error' and timeout) to reject the promise if the
connection ends before a response, introduce a boolean "settled" flag to prevent
multiple resolve/reject calls, wrap JSON.parse(line) in try/catch and reject the
promise on parse errors instead of throwing, and replace any require('net')
usage in sendRawLine with the imported net variable; ensure each handler (data,
error, close, timeout) checks/sets the settled flag and properly destroys/ends
the socket when finishing.
- Line 40: The code uses require('net').createConnection at the socket
connection site; change that to use the already-imported ES module variable net
(i.e., replace require('net').createConnection(...) with
net.createConnection(...)) so the call in the connection callback uses the
existing net identifier and keeps import style consistent (look for the
createConnection call around the client variable initialization).

In `@apps/desktop/src/main/daemon/daemon-inprocess-handlers.ts`:
- Around line 97-99: The RPC handlers registered for 'task.start' and
'session.resume' destructure params directly (e.g., const { taskId:
providedTaskId, config } = params) which will throw on null/malformed JSON-RPC
params; update both handlers in srv.registerMethod to first validate that params
is a non-null object (and has expected keys/types) before destructuring, and
return or throw a controlled RPC error (with a clear message) when validation
fails so malformed input doesn't cause an uncaught exception; keep references to
the existing helpers like createTaskId() and the same parameter names when
applying the guarded destructuring.

---

Duplicate comments:
In `@apps/desktop/src/main/daemon/daemon-inprocess-handlers.ts`:
- Around line 128-139: The current try/catch wraps taskManager.startTask and the
subsequent storage writes so a storage write error after a successful start can
mark a resumed task as 'failed'; change the flow in daemon-inprocess-handlers.ts
so you first await taskManager.startTask(taskId, { prompt, sessionId },
callbacks) inside its own try/catch and only set status to 'failed' there if
startTask throws, then perform storage.saveTask(task) or
storage.updateTaskStatus(existingTaskId, ...) in a separate try/catch that
handles/publishes persistence errors without mutating the task status for
resumed tasks (i.e., do not call storage.updateTaskStatus(taskId, 'failed', ...)
on storage write failures); refer to taskManager.startTask, storage.saveTask,
storage.updateTaskStatus, and existingTaskId/session.resume to locate and
implement this change.
- Around line 33-35: Several single-line braceless conditionals remain (e.g., in
the srv.registerMethod('task.delete', (params) => { if (params)
storage.deleteTask(params.taskId); }); handler) and must be changed to use
braces to satisfy the `curly` lint rule; update each braceless conditional
(including similar patterns around the other registerMethod handlers referenced)
to a block form like: if (params) { storage.deleteTask(params.taskId); } (apply
the same change to all other single-line if/else statements in this file such as
the handlers covering the other mentioned ranges).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 77ca9b09-4ab5-4eed-9f18-60b9b6d74a55

📥 Commits

Reviewing files that changed from the base of the PR and between 0de9da2 and f3c13ec.

📒 Files selected for processing (7)
  • apps/desktop/__tests__/unit/main/daemon/helpers/server.helpers.ts
  • apps/desktop/__tests__/unit/main/daemon/server.unit.test.ts
  • apps/desktop/src/main/daemon/daemon-inprocess-handlers.ts
  • apps/desktop/src/main/ipc/task-callbacks.ts
  • apps/desktop/src/preload/index.ts
  • apps/web/src/client/lib/accomplish.ts
  • packages/agent-core/src/index.ts
✅ Files skipped from review due to trivial changes (2)
  • apps/web/src/client/lib/accomplish.ts
  • apps/desktop/tests/unit/main/daemon/server.unit.test.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/desktop/src/preload/index.ts
  • apps/desktop/src/main/ipc/task-callbacks.ts

Comment thread apps/desktop/__tests__/unit/main/daemon/helpers/server.helpers.ts Outdated
Comment thread apps/desktop/__tests__/unit/main/daemon/helpers/server.helpers.ts Outdated
Comment thread apps/desktop/src/main/daemon/daemon-inprocess-handlers.ts
…}, customText preserved, scoped cleanup in daemon-spawn, fix import in task-callbacks, update lockfile

@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 the current code and only fix it if needed.

Inline comments:
In `@apps/desktop/src/main/daemon/rpc-dispatcher.ts`:
- Around line 77-150: The async response writes in handleLine (where writeFn is
invoked in the Promise .then() and .catch() branches) must be hardened: create
or use a safeWrite wrapper that first checks the underlying stream/socket
writable state (e.g., socket.writable) before attempting to write, wraps the
writeFn call in try/catch to handle synchronous throws (ERR_STREAM_DESTROYED),
handles a false return by waiting for a 'drain' or failing gracefully, and
listens for asynchronous 'error' events so writes don't crash the process;
replace direct calls to writeFn(JSON.stringify(...)+ '\n') in handleLine (and in
the handler-not-found/error branches) with calls to this safeWrite helper and
ensure notifications still quietly drop if the socket is not writable.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f94c6b2f-b82d-44f9-a997-f39892ca8a21

📥 Commits

Reviewing files that changed from the base of the PR and between f3c13ec and 54e9d2d.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (4)
  • apps/desktop/src/main/daemon/daemon-inprocess-permissions.ts
  • apps/desktop/src/main/daemon/daemon-spawn.ts
  • apps/desktop/src/main/daemon/rpc-dispatcher.ts
  • apps/desktop/src/main/ipc/task-callbacks.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/desktop/src/main/ipc/task-callbacks.ts
  • apps/desktop/src/main/daemon/daemon-inprocess-permissions.ts

Comment thread apps/desktop/src/main/daemon/rpc-dispatcher.ts
@mavishay

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Mar 23, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@mavishay
mavishay merged commit 32b1f33 into main Mar 23, 2026
10 of 11 checks passed
@mavishay
mavishay deleted the feat/ENG-694-daemon-architecture-consolidated branch March 23, 2026 07:09
@coderabbitai coderabbitai Bot mentioned this pull request Mar 23, 2026
11 tasks
github-actions Bot pushed a commit that referenced this pull request Mar 23, 2026
…nsolidated) (#770)

* feat(daemon): core JSON-RPC daemon infrastructure (ENG-694)

From PR #626 by david-mamani (David Abdiel Mamani Chuquimamani):
- packages/agent-core/src/common/types/daemon.ts: daemon protocol types
- packages/agent-core/src/daemon/server.ts: DaemonServer JSON-RPC dispatcher
- packages/agent-core/src/daemon/client.ts: DaemonClient typed RPC caller
- packages/agent-core/src/daemon/transport.ts: in-process transport pair
- packages/agent-core/src/daemon/ipc-transport.ts: child_process IPC transport
- packages/agent-core/src/daemon/scheduler.ts: cron-based task scheduler
- packages/agent-core/src/daemon/index.ts: public exports
- apps/desktop/src/main/daemon-bootstrap.ts: bootstrap daemon lifecycle
- apps/desktop/src/main/daemon/cli-bridge.ts: CLI JSON-RPC bridge
- apps/desktop/src/main/daemon/entry.ts: daemon entry point
- apps/desktop/src/main/daemon/service-manager.ts: auto-start service
- apps/desktop/src/main/tray.ts: system tray with task count
- apps/desktop/e2e/specs/daemon.spec.ts: E2E tests

Closes #402
Ref: ENG-694

* feat(daemon): UI settings panel, socket server, background mode (ENG-694)

From PR #613 by SaaiAravindhRaja / ChaiAndCode:
- apps/desktop/src/main/daemon/server.ts: Unix socket / named pipe server
  with JSON-RPC 2.0, supports daemon.ping, task.start, task.schedule, etc.
- apps/desktop/src/main/ipc/task-callbacks.ts: add DaemonTaskCallbacksOptions
  and createDaemonTaskCallbacks() for background task execution with system
  notifications when window is hidden
- apps/desktop/__tests__/unit/main/daemon/scheduler.unit.test.ts: scheduler tests
- apps/desktop/__tests__/unit/main/daemon/server.unit.test.ts: server unit tests
- apps/web/src/client/components/settings/DaemonPanel.tsx: Settings UI panel
  with background mode toggle and socket path display
- apps/web/src/client/components/ui/switch.tsx: accessible Switch component
- apps/web/locales/en|zh-CN/settings.json: add 'daemon' tab i18n key
- apps/web/src/client/components/layout/SettingsDialog.tsx: add Daemon tab
- apps/web/src/client/lib/accomplish.ts: add daemon API interface methods
- apps/desktop/src/preload/index.ts: expose daemon IPC methods to renderer
- apps/desktop/src/main/index.ts: integrate daemon bootstrap + tray init
- apps/desktop/src/main/ipc/handlers/settings-handlers.ts: IPC handlers
  for daemon:get-run-in-background, daemon:set-run-in-background, daemon:get-socket-path
- packages/agent-core/src/storage/migrations/v013-daemon.ts: DB migration
  to add run_in_background column to app_settings
- packages/agent-core/src/storage/repositories/appSettings.ts: add
  getRunInBackground / setRunInBackground CRUD
- packages/agent-core/src/types/storage.ts: extend AppSettings + AppSettingsAPI

Ref: ENG-694

* feat(daemon): standalone HTTP daemon app with rate limiting and services (ENG-694)

From PR #598 by aryan877 (Aryan):
- apps/daemon/package.json: @accomplish/daemon standalone app config
- apps/daemon/src/index.ts: daemon entry point with HTTP server
- apps/daemon/src/cli.ts: CLI argument parsing and startup
- apps/daemon/src/health.ts: health check endpoint (/health)
- apps/daemon/src/http-server-factory.ts: HTTP server with auth token
  validation (timing-safe), request size limits, route dispatch
- apps/daemon/src/rate-limiter.ts: sliding window rate limiter per IP
- apps/daemon/src/permission-service.ts: permission request management
- apps/daemon/src/storage-service.ts: persistent storage service
- apps/daemon/src/task-service.ts: task creation and management
- apps/daemon/src/thought-stream-service.ts: thought stream events
- apps/daemon/tsconfig.json: TypeScript config for daemon app
- apps/daemon/tsup.config.ts: tsup build config
- apps/daemon/vitest.config.ts: vitest test config
- apps/daemon/__tests__/unit/health.test.ts: health endpoint tests
- apps/daemon/__tests__/unit/http-server-factory.test.ts: server factory tests
- apps/daemon/__tests__/unit/rate-limiter.test.ts: rate limiter tests
- docs/architecture.md: daemon architecture documentation

Key features: Bearer token auth, 1MB request body limit, 429 rate limiting,
per-IP sliding window with automatic cleanup.

Ref: ENG-694

* fix: address CodeRabbit comments on PR #770

- websocket.ts: wrap message handler in try/catch with shape validation
- daemon-bootstrap.ts: register missing task.start/session.resume/permission.respond RPCs
- pnpm-lock.yaml: regenerate after adding daemon package
Addresses: 2971335381, 2971335383, 2971335385

* fix: address CodeRabbit comments on PR #770 (round 2)

- daemon-bootstrap.ts: replace console.log/warn/error with getLogCollector().logEnv()
- daemon-bootstrap.ts: reject promise immediately on pre-ready child process exit
- daemon-bootstrap.ts: persist task to storage before calling startTask (task.start + session.resume)
Addresses: 2971423605, 2971423607, 2971423609
Skipped  : 2971423602

* fix: export DaemonRpcServer, getSocketPath, acquirePidLock, installCrashHandlers from agent-core

* fix: address CodeRabbit review comments for PR #770 - exports, fallback handlers, ESM fix, error handling

- scheduler.ts: replace console.log with structured logger
- switch.tsx: rewrite using @radix-ui/react-switch with CVA variants
- vitest.config.ts: fix __dirname in ESM context using fileURLToPath
- daemon-bootstrap.ts: task.cancelQueued now persists cancelled status to storage
- daemon-bootstrap.ts: permission.respond now resolves requestId-based permissions
  via in-process permission handler (resolvePermission/resolveQuestion)
- daemon/types: add task.stop, task.status, health.check to DaemonMethodMap
- daemon/types: add task.thought, task.checkpoint, task.error to DaemonNotificationMap
- daemon/types: add HealthCheckResult interface
- config-generator.ts: add authToken field to ConfigGeneratorOptions
- package.json: add ws and @types/ws dependencies to daemon

* fix: use double cast for placeholder task in daemon-bootstrap to satisfy TypeScript

* style: apply prettier formatting to pre-existing daemon files

* fix: export parseCronField and matchesCron, add cron validation, fix cancelScheduledTask return type

* fix: implement cron OR semantics for dom/dow in matchesCron

* fix: add apps/daemon/package.json to E2E Docker build context

* fix: make daemon server unit tests platform-aware (Windows named pipe timing)

* fix: update pnpm-lock.yaml after merge

* fix: address CodeRabbit review comments on daemon architecture

- Extract RPC dispatch logic from server.ts to rpc-dispatcher.ts to keep
  server.ts under 200 lines and focused on socket lifecycle (fix #2972626290)
- Add isValidJsonRpcRequest type guard returning -32600 Invalid Request for
  malformed JSON-RPC envelopes (fix #2972626292)
- Add MAX_SOCKET_BUFFER_BYTES (1MB) guard with socket.destroy on overflow
  (fix #2972626296)
- Replace all console.log/warn/error with getLogCollector() app logger in
  server.ts and rpc-dispatcher.ts (fix #2972626299)
- Log instead of silently swallow daemon init errors in DaemonPanel
  (fix #2972626303)
- Add explicit catch block in handleToggle to log save failures and keep
  local state unchanged on error (fix #2972626307)
- Rename Switch onChange prop to onCheckedChange to align with Radix
  Switch.Root API, update DaemonPanel caller (fix #2972626318)
- Consolidate RpcMethodMap/DaemonMethodMap by aliasing RpcMethodMap to
  DaemonMethodMap to prevent type divergence (fix #2972626325)

* fix: add 'daemon' to LogSource union type

* fix: add 'daemon' to LOG_SOURCE_PATTERNS

* fix: safe logger fallback in daemon server/dispatcher — handles uninitialized LogCollector in tests

* fix: skip socket-based daemon tests on Windows (named pipe timing)

* fix(ENG-1002): split daemon-bootstrap.ts into daemon-spawn, daemon-inprocess, daemon-lifecycle (#784)

Co-authored-by: Avishay Maor <avishaym@tikalk.com>

* fix: remove console fallback in safeLog, sanitize RPC error response data

* fix: update test expectation to match sanitized RPC error response

* fix: split server.unit.test.ts helper, fix TOCTOU cancel, braceless ifs, separate start/persist failures

* fix: braceless ifs in isValidJsonRpcRequest, don't coerce params to {}, customText preserved, scoped cleanup in daemon-spawn, fix import in task-callbacks, update lockfile

* fix: socket lifecycle in test helpers, guard params before destructure, safeWrite in rpc-dispatcher, socket.writable check in server

* fix: restore net import in server.unit.test.ts (still used for notification test)

---------

Co-authored-by: mavishay <mavishay@github.com>
Co-authored-by: Avishay Mashiach <avishaym@tikalk.com>
yanaifranchi-tech pushed a commit that referenced this pull request Apr 16, 2026
Deletes the entire `report-thought` / `report-checkpoint` pipeline,
which has been dead since 2026-01-26 (commit a48afa5 by Daniel
Scharfstein — "refactor: remove dev-browser MCP, all skills, and
system prompts"). That commit removed the MCP-server registration
from the desktop's config-generator; since then, neither tool has
been registered in opencode's per-task config, so the LLM has had
no way to invoke either of them.

During the later daemon migration (#770, #847) the infrastructure
was partially rebuilt on the daemon side — new ThoughtStreamService
on :9228, new agent-core handler classes, RPC forwarders,
`registerTask`/`unregisterTask` plumbing, desktop IPC forwarders —
but the renderer-side consumer was never restored. Net result:
an HTTP server receiving zero real traffic, forwarders firing never,
and no subscriber at the end of the pipe even if they did.

Deleted files (11):
  packages/agent-core/mcp-tools/report-thought/ (full dir)
  packages/agent-core/mcp-tools/report-checkpoint/ (full dir)
  apps/daemon/src/thought-stream-service.ts
  packages/agent-core/src/internal/classes/ThoughtStreamHandler.ts
  packages/agent-core/src/services/thought-stream-handler.ts  (duplicate of the above — byte-identical)
  packages/agent-core/src/factories/thought-stream.ts
  packages/agent-core/src/types/thought-stream.ts
  packages/agent-core/src/common/types/thought-stream.ts  (second duplicate)
  apps/daemon/src/websocket.ts  (see "Also removed" below)

Stripped from the agent-core public API surface:
  - createThoughtStreamHandler factory
  - ThoughtStreamAPI / ThoughtStreamOptions / ThoughtEvent /
    CheckpointEvent / ThoughtCategory / CheckpointStatus types
  - ThoughtStreamEvent / ThoughtStreamCheckpointEvent aliases
  - THOUGHT_STREAM_PORT constant

Stripped from daemon wiring:
  - ThoughtStreamService construction, start, shutdown
  - setEventHandlers RPC forwarder
  - task-event-forwarding register/unregister per-task calls
  - env var propagation (ACCOMPLISH_THOUGHT_STREAM_PORT,
    THOUGHT_STREAM_PORT for child MCP processes)
  - thoughtStreamService field on RouteServices

Stripped from desktop wiring:
  - client.onNotification('task.thought' | 'task.checkpoint', …)
    forwarders in daemon-bootstrap
  - outdated comment in app-startup referencing the deleted events

Stripped from shared type maps:
  - 'task.thought' / 'task.checkpoint' / 'thought.event' /
    'checkpoint.event' entries on RpcNotificationMap (two copies,
    one in common/types/daemon.ts and one in daemon/types.ts)

Stripped from tool classification + UI:
  - 'report_thought' / 'report_checkpoint' from
    NON_TASK_CONTINUATION_TOOLS (the tools don't exist as invocable,
    so they can't trigger continuation classification regardless)
  - report_thought / report_checkpoint entries in tool-mappings.ts
    (unused label/icon map — Lightbulb and Flag icon imports removed
    too since they have no remaining referents)

Also removed: unused daemon WebSocket helper
  During cleanup, discovered `apps/daemon/src/websocket.ts` had
  zero runtime consumers (verified by static search: no imports of
  setupWebSocket/broadcast/onClientMessage/DaemonEvent, not a build
  entrypoint, not referenced by tests). The DaemonEvent union still
  advertised 'task:thought' / 'task:checkpoint' variants for the
  deleted pipeline, so fixing those variants meant touching the
  file anyway. Deleted the whole module — the daemon's active
  transport is JSON-RPC over a Unix socket / named pipe, not
  WebSocket. A WebSocket-based daemon interface can be re-added
  later if it becomes necessary; until then, leaving the stub
  around is just a stale reference surface.

Docs updated:
  - docs/functional-viewpoint.md (9 stale refs in Overview,
    Detailed Map, Channel Map, and Component Responsibility Matrix,
    removed; also fixed a typo where I wrote ":9229 WhatsApp send"
    — actual constant is WHATSAPP_API_PORT = 9230)
  - docs/qa-suites/task-execution-tests.md — removed EXEC-SDK-10
    (regression check for the now-deleted flow)
  - docs/development-viewpoint.md — removed report-thought /
    report-checkpoint from the mcp-tools listing and the
    non-existent `useThoughtStream` hook from the client-dir map
  - docs/dev-processes-diagram.md — removed `HTTP :9228 (thought
    stream)` line from the daemon-process node
  - packages/agent-core/README.md — removed createThoughtStreamHandler
    API section

Docs NOT updated in this PR (out of scope):
  docs/daemon-final-architecture.md, docs/information-viewpoint.md,
  docs/concurrency-viewpoint.md, docs/daemon-code-audit.md all
  carry pre-SDK-cutover HISTORICAL banners — their thought-stream
  mentions are part of their historical record of what existed
  pre-cutover, not current-state claims. Belongs to a separate
  historical-docs-sweep PR.

Side benefit — latent port collision fixed:
  THOUGHT_STREAM_PORT was hardcoded to 9228, same as
  AZURE_FOUNDRY_PROXY_PORT (also hardcoded in
  azure-foundry-proxy.ts:15). If a user activated Azure Foundry
  while the daemon was up, the two would collide on bind.
  Removing THOUGHT_STREAM_PORT (and the service that bound it)
  eliminates the collision entirely.

Net diff: −4208 / +14 on first commit, plus this amendment.

Validation run:
  pnpm typecheck                        — clean (4 workspaces)
  pnpm lint:eslint                      — 0 errors
  pnpm format:check                     — clean
  pnpm -F @accomplish_ai/agent-core test — 619/619 + 1 skipped
  pnpm -F @accomplish/daemon test       — 110/110
  pnpm -F @accomplish/web test:unit     — 358/358
  pnpm -F @accomplish/desktop test:unit — 352/352
  pnpm build                            — success

Zero-grep across apps/ packages/ (src only) + the current-state
docs for thought-stream / ThoughtStream / THOUGHT_STREAM /
report-thought / report-checkpoint / report_thought /
report_checkpoint / ThoughtEvent / CheckpointEvent /
ThoughtCategory / CheckpointStatus / createThoughtStreamHandler /
useThoughtStream returns only a single intentional documentation
comment in apps/daemon/src/index.ts and the "What's gone vs.
PTY-era" bullet in functional-viewpoint.md that explicitly
documents the removal — no orphan references remain.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
yanaifranchi-tech added a commit that referenced this pull request Apr 16, 2026
Deletes the entire `report-thought` / `report-checkpoint` pipeline,
which has been dead since 2026-01-26 (commit a48afa5 by Daniel
Scharfstein — "refactor: remove dev-browser MCP, all skills, and
system prompts"). That commit removed the MCP-server registration
from the desktop's config-generator; since then, neither tool has
been registered in opencode's per-task config, so the LLM has had
no way to invoke either of them.

During the later daemon migration (#770, #847) the infrastructure
was partially rebuilt on the daemon side — new ThoughtStreamService
on :9228, new agent-core handler classes, RPC forwarders,
`registerTask`/`unregisterTask` plumbing, desktop IPC forwarders —
but the renderer-side consumer was never restored. Net result:
an HTTP server receiving zero real traffic, forwarders firing never,
and no subscriber at the end of the pipe even if they did.

Deleted files (11):
  packages/agent-core/mcp-tools/report-thought/ (full dir)
  packages/agent-core/mcp-tools/report-checkpoint/ (full dir)
  apps/daemon/src/thought-stream-service.ts
  packages/agent-core/src/internal/classes/ThoughtStreamHandler.ts
  packages/agent-core/src/services/thought-stream-handler.ts  (duplicate of the above — byte-identical)
  packages/agent-core/src/factories/thought-stream.ts
  packages/agent-core/src/types/thought-stream.ts
  packages/agent-core/src/common/types/thought-stream.ts  (second duplicate)
  apps/daemon/src/websocket.ts  (see "Also removed" below)

Stripped from the agent-core public API surface:
  - createThoughtStreamHandler factory
  - ThoughtStreamAPI / ThoughtStreamOptions / ThoughtEvent /
    CheckpointEvent / ThoughtCategory / CheckpointStatus types
  - ThoughtStreamEvent / ThoughtStreamCheckpointEvent aliases
  - THOUGHT_STREAM_PORT constant

Stripped from daemon wiring:
  - ThoughtStreamService construction, start, shutdown
  - setEventHandlers RPC forwarder
  - task-event-forwarding register/unregister per-task calls
  - env var propagation (ACCOMPLISH_THOUGHT_STREAM_PORT,
    THOUGHT_STREAM_PORT for child MCP processes)
  - thoughtStreamService field on RouteServices

Stripped from desktop wiring:
  - client.onNotification('task.thought' | 'task.checkpoint', …)
    forwarders in daemon-bootstrap
  - outdated comment in app-startup referencing the deleted events

Stripped from shared type maps:
  - 'task.thought' / 'task.checkpoint' / 'thought.event' /
    'checkpoint.event' entries on RpcNotificationMap (two copies,
    one in common/types/daemon.ts and one in daemon/types.ts)

Stripped from tool classification + UI:
  - 'report_thought' / 'report_checkpoint' from
    NON_TASK_CONTINUATION_TOOLS (the tools don't exist as invocable,
    so they can't trigger continuation classification regardless)
  - report_thought / report_checkpoint entries in tool-mappings.ts
    (unused label/icon map — Lightbulb and Flag icon imports removed
    too since they have no remaining referents)

Also removed: unused daemon WebSocket helper
  During cleanup, discovered `apps/daemon/src/websocket.ts` had
  zero runtime consumers (verified by static search: no imports of
  setupWebSocket/broadcast/onClientMessage/DaemonEvent, not a build
  entrypoint, not referenced by tests). The DaemonEvent union still
  advertised 'task:thought' / 'task:checkpoint' variants for the
  deleted pipeline, so fixing those variants meant touching the
  file anyway. Deleted the whole module — the daemon's active
  transport is JSON-RPC over a Unix socket / named pipe, not
  WebSocket. A WebSocket-based daemon interface can be re-added
  later if it becomes necessary; until then, leaving the stub
  around is just a stale reference surface.

Docs updated:
  - docs/functional-viewpoint.md (9 stale refs in Overview,
    Detailed Map, Channel Map, and Component Responsibility Matrix,
    removed; also fixed a typo where I wrote ":9229 WhatsApp send"
    — actual constant is WHATSAPP_API_PORT = 9230)
  - docs/qa-suites/task-execution-tests.md — removed EXEC-SDK-10
    (regression check for the now-deleted flow)
  - docs/development-viewpoint.md — removed report-thought /
    report-checkpoint from the mcp-tools listing and the
    non-existent `useThoughtStream` hook from the client-dir map
  - docs/dev-processes-diagram.md — removed `HTTP :9228 (thought
    stream)` line from the daemon-process node
  - packages/agent-core/README.md — removed createThoughtStreamHandler
    API section

Docs NOT updated in this PR (out of scope):
  docs/daemon-final-architecture.md, docs/information-viewpoint.md,
  docs/concurrency-viewpoint.md, docs/daemon-code-audit.md all
  carry pre-SDK-cutover HISTORICAL banners — their thought-stream
  mentions are part of their historical record of what existed
  pre-cutover, not current-state claims. Belongs to a separate
  historical-docs-sweep PR.

Side benefit — latent port collision fixed:
  THOUGHT_STREAM_PORT was hardcoded to 9228, same as
  AZURE_FOUNDRY_PROXY_PORT (also hardcoded in
  azure-foundry-proxy.ts:15). If a user activated Azure Foundry
  while the daemon was up, the two would collide on bind.
  Removing THOUGHT_STREAM_PORT (and the service that bound it)
  eliminates the collision entirely.

Net diff: −4208 / +14 on first commit, plus this amendment.

Validation run:
  pnpm typecheck                        — clean (4 workspaces)
  pnpm lint:eslint                      — 0 errors
  pnpm format:check                     — clean
  pnpm -F @accomplish_ai/agent-core test — 619/619 + 1 skipped
  pnpm -F @accomplish/daemon test       — 110/110
  pnpm -F @accomplish/web test:unit     — 358/358
  pnpm -F @accomplish/desktop test:unit — 352/352
  pnpm build                            — success

Zero-grep across apps/ packages/ (src only) + the current-state
docs for thought-stream / ThoughtStream / THOUGHT_STREAM /
report-thought / report-checkpoint / report_thought /
report_checkpoint / ThoughtEvent / CheckpointEvent /
ThoughtCategory / CheckpointStatus / createThoughtStreamHandler /
useThoughtStream returns only a single intentional documentation
comment in apps/daemon/src/index.ts and the "What's gone vs.
PTY-era" bullet in functional-viewpoint.md that explicitly
documents the removal — no orphan references remain.

Co-authored-by: Yanai Franchi <yanai@tikalk.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
github-actions Bot pushed a commit that referenced this pull request Apr 16, 2026
Deletes the entire `report-thought` / `report-checkpoint` pipeline,
which has been dead since 2026-01-26 (commit a48afa5 by Daniel
Scharfstein — "refactor: remove dev-browser MCP, all skills, and
system prompts"). That commit removed the MCP-server registration
from the desktop's config-generator; since then, neither tool has
been registered in opencode's per-task config, so the LLM has had
no way to invoke either of them.

During the later daemon migration (#770, #847) the infrastructure
was partially rebuilt on the daemon side — new ThoughtStreamService
on :9228, new agent-core handler classes, RPC forwarders,
`registerTask`/`unregisterTask` plumbing, desktop IPC forwarders —
but the renderer-side consumer was never restored. Net result:
an HTTP server receiving zero real traffic, forwarders firing never,
and no subscriber at the end of the pipe even if they did.

Deleted files (11):
  packages/agent-core/mcp-tools/report-thought/ (full dir)
  packages/agent-core/mcp-tools/report-checkpoint/ (full dir)
  apps/daemon/src/thought-stream-service.ts
  packages/agent-core/src/internal/classes/ThoughtStreamHandler.ts
  packages/agent-core/src/services/thought-stream-handler.ts  (duplicate of the above — byte-identical)
  packages/agent-core/src/factories/thought-stream.ts
  packages/agent-core/src/types/thought-stream.ts
  packages/agent-core/src/common/types/thought-stream.ts  (second duplicate)
  apps/daemon/src/websocket.ts  (see "Also removed" below)

Stripped from the agent-core public API surface:
  - createThoughtStreamHandler factory
  - ThoughtStreamAPI / ThoughtStreamOptions / ThoughtEvent /
    CheckpointEvent / ThoughtCategory / CheckpointStatus types
  - ThoughtStreamEvent / ThoughtStreamCheckpointEvent aliases
  - THOUGHT_STREAM_PORT constant

Stripped from daemon wiring:
  - ThoughtStreamService construction, start, shutdown
  - setEventHandlers RPC forwarder
  - task-event-forwarding register/unregister per-task calls
  - env var propagation (ACCOMPLISH_THOUGHT_STREAM_PORT,
    THOUGHT_STREAM_PORT for child MCP processes)
  - thoughtStreamService field on RouteServices

Stripped from desktop wiring:
  - client.onNotification('task.thought' | 'task.checkpoint', …)
    forwarders in daemon-bootstrap
  - outdated comment in app-startup referencing the deleted events

Stripped from shared type maps:
  - 'task.thought' / 'task.checkpoint' / 'thought.event' /
    'checkpoint.event' entries on RpcNotificationMap (two copies,
    one in common/types/daemon.ts and one in daemon/types.ts)

Stripped from tool classification + UI:
  - 'report_thought' / 'report_checkpoint' from
    NON_TASK_CONTINUATION_TOOLS (the tools don't exist as invocable,
    so they can't trigger continuation classification regardless)
  - report_thought / report_checkpoint entries in tool-mappings.ts
    (unused label/icon map — Lightbulb and Flag icon imports removed
    too since they have no remaining referents)

Also removed: unused daemon WebSocket helper
  During cleanup, discovered `apps/daemon/src/websocket.ts` had
  zero runtime consumers (verified by static search: no imports of
  setupWebSocket/broadcast/onClientMessage/DaemonEvent, not a build
  entrypoint, not referenced by tests). The DaemonEvent union still
  advertised 'task:thought' / 'task:checkpoint' variants for the
  deleted pipeline, so fixing those variants meant touching the
  file anyway. Deleted the whole module — the daemon's active
  transport is JSON-RPC over a Unix socket / named pipe, not
  WebSocket. A WebSocket-based daemon interface can be re-added
  later if it becomes necessary; until then, leaving the stub
  around is just a stale reference surface.

Docs updated:
  - docs/functional-viewpoint.md (9 stale refs in Overview,
    Detailed Map, Channel Map, and Component Responsibility Matrix,
    removed; also fixed a typo where I wrote ":9229 WhatsApp send"
    — actual constant is WHATSAPP_API_PORT = 9230)
  - docs/qa-suites/task-execution-tests.md — removed EXEC-SDK-10
    (regression check for the now-deleted flow)
  - docs/development-viewpoint.md — removed report-thought /
    report-checkpoint from the mcp-tools listing and the
    non-existent `useThoughtStream` hook from the client-dir map
  - docs/dev-processes-diagram.md — removed `HTTP :9228 (thought
    stream)` line from the daemon-process node
  - packages/agent-core/README.md — removed createThoughtStreamHandler
    API section

Docs NOT updated in this PR (out of scope):
  docs/daemon-final-architecture.md, docs/information-viewpoint.md,
  docs/concurrency-viewpoint.md, docs/daemon-code-audit.md all
  carry pre-SDK-cutover HISTORICAL banners — their thought-stream
  mentions are part of their historical record of what existed
  pre-cutover, not current-state claims. Belongs to a separate
  historical-docs-sweep PR.

Side benefit — latent port collision fixed:
  THOUGHT_STREAM_PORT was hardcoded to 9228, same as
  AZURE_FOUNDRY_PROXY_PORT (also hardcoded in
  azure-foundry-proxy.ts:15). If a user activated Azure Foundry
  while the daemon was up, the two would collide on bind.
  Removing THOUGHT_STREAM_PORT (and the service that bound it)
  eliminates the collision entirely.

Net diff: −4208 / +14 on first commit, plus this amendment.

Validation run:
  pnpm typecheck                        — clean (4 workspaces)
  pnpm lint:eslint                      — 0 errors
  pnpm format:check                     — clean
  pnpm -F @accomplish_ai/agent-core test — 619/619 + 1 skipped
  pnpm -F @accomplish/daemon test       — 110/110
  pnpm -F @accomplish/web test:unit     — 358/358
  pnpm -F @accomplish/desktop test:unit — 352/352
  pnpm build                            — success

Zero-grep across apps/ packages/ (src only) + the current-state
docs for thought-stream / ThoughtStream / THOUGHT_STREAM /
report-thought / report-checkpoint / report_thought /
report_checkpoint / ThoughtEvent / CheckpointEvent /
ThoughtCategory / CheckpointStatus / createThoughtStreamHandler /
useThoughtStream returns only a single intentional documentation
comment in apps/daemon/src/index.ts and the "What's gone vs.
PTY-era" bullet in functional-viewpoint.md that explicitly
documents the removal — no orphan references remain.

Co-authored-by: Yanai Franchi <yanai@tikalk.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature] Daemon Architecture

2 participants