Commit Graph

76 Commits

Author SHA1 Message Date
hailin 5d4fd96d43 feat: streaming claude-api engine, engineType override, fix voice test page
- Claude API engine now uses streaming API (messages.stream) for real-time
  text delta output instead of waiting for full response
- Agent controller accepts optional engineType body parameter to allow
  callers (e.g. voice pipeline) to select a specific engine
- Fix voice_test_page.dart compilation error: replace audioplayers (not
  installed) with flutter_sound (already in pubspec.yaml)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 05:30:11 -08:00
hailin 79fae0629e chore: upgrade claude-agent-sdk to ^0.2.52
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 04:12:03 -08:00
hailin 2a150dcff5 fix: prevent error event from overriding completed status in controller
Add finished guard so that once a task reaches completed/error terminal
state, subsequent events don't flip the status back.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 03:49:21 -08:00
hailin 8e4bd573f4 fix: deduplicate text events from SDK stream_event and assistant message
SDK sends text both via stream_event deltas (token-level) and assistant
message (complete block). Track hasStreamedText flag per session to skip
duplicate text extraction from assistant messages.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 03:31:48 -08:00
hailin aa2a49afd4 fix: extract text from assistant message + fix event data parsing
Root causes found:
1. SDK engine only emitted 'completed' without 'text' events because
   mapSdkMessage skipped text blocks in 'assistant' messages (assumed
   stream_event deltas would provide them, but SDK didn't send deltas)
2. Voice pipeline read evt_data.data.content but engine events are flat
   (evt_data.content) — so even if text arrived, it was never extracted

Fixes:
- Extract text/thinking blocks from assistant messages in SDK engine
- Fix voice pipeline to read content directly from evt_data, not nested

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 03:01:25 -08:00
hailin a7b42e6b98 feat: add detailed logging to agent engine and task controller
Log every SDK message type, event emission, and stream lifecycle
to diagnose why text events are missing in voice-agent flow.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 02:56:09 -08:00
hailin 1d5c834dfe feat: add event buffering to agent WS gateway for late subscribers
Buffer stream events when no WS clients are subscribed yet, then replay
them when a client subscribes. This eliminates the race condition where
events are lost between task creation and WS subscription.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 02:41:38 -08:00
hailin 74be945e4a feat: enable token-level streaming and fix duplicate message bubble
Backend:
- Add includePartialMessages: true to SDK query options
- Handle stream_event/content_block_delta for real-time text streaming
- Skip text/thinking blocks from complete assistant messages (already
  streamed via deltas) to avoid duplication
- Change default result summary to empty string

Flutter:
- Only show CompletedEvent summary when no assistant text was streamed
  (prevents duplicate message bubble)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 17:24:48 -08:00
hailin 86d7cac631 fix: replace Socket.IO with raw WebSocket to fix 502 on /ws/agent
Socket.IO requires its own handshake protocol (EIO=4) which Kong cannot
proxy as a plain WebSocket upgrade, causing 502 Bad Gateway. Switch to
@nestjs/platform-ws (WsAdapter) with manual session room tracking so
Flutter's IOWebSocketChannel can connect directly.

Also add ws/wss protocols to Kong WebSocket routes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 16:52:43 -08:00
hailin 3cb9ebd407 fix: release QueryRunner connections to prevent pool exhaustion
TenantAwareRepository.getRepository() was calling createQueryRunner()
without ever releasing it, causing database connection pool exhaustion.
This caused ops-service (and eventually other services) to hang on
all API requests once the pool filled up.

Replaced getRepository() with withRepository() pattern that wraps
operations in try/finally to always release the QueryRunner.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 15:55:06 -08:00
hailin d4391eef97 fix: run services as non-root user for SDK bypassPermissions
SDK blocks bypassPermissions when running as root for security.
Add non-root 'appuser' to Dockerfile.service and update volume
mounts to use /home/appuser/.claude paths.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 06:41:10 -08:00
hailin 04a18a7899 fix: use acceptEdits mode and mount .claude.json for SDK
- bypassPermissions blocked by SDK when running as root
- Switch to acceptEdits with canUseTool for programmatic control
- Mount .claude.json config file into container

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 06:37:31 -08:00
hailin db1d0620f2 debug: add stderr callback to SDK engine for error visibility
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 06:34:42 -08:00
hailin d40f66ce14 fix: use bypassPermissions mode for headless SDK execution
In a Docker container without TTY, permissionMode 'default' blocks
waiting for interactive permission prompts. Switch to bypassPermissions
with canUseTool callback for programmatic risk-based access control.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 06:30:38 -08:00
hailin 14e8d7019a fix: use dynamic import helper for ESM-only claude-agent-sdk
tsc with module=commonjs converts `await import()` to require(),
which breaks ESM-only packages. Use Function('return import()')
workaround to preserve native dynamic import at runtime.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 06:18:04 -08:00
hailin b963b7d4da feat: enable SDK subscription mode with OAuth credentials mount
- Mount ~/.claude/ into agent-service container for OAuth token access
- Switch default engine to claude_agent_sdk
- Remove ANTHROPIC_API_KEY from env in subscription mode so SDK uses OAuth
- Keep API key mode for per-tenant billing

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 06:14:45 -08:00
hailin 9126225317 fix: disable TLS verification for Anthropic proxy (self-signed cert)
Follow iConsulting pattern: set NODE_TLS_REJECT_UNAUTHORIZED=0 when
ANTHROPIC_BASE_URL is configured, enabling connection through the
self-signed proxy at 67.223.119.33.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 05:52:02 -08:00
hailin 810dcd7def feat: switch default engine to claude_api with base URL support
- Change AGENT_ENGINE_TYPE from claude_code_cli to claude_api in docker-compose
- Add ANTHROPIC_BASE_URL env var support to claude-api-engine
- Add ANTHROPIC_BASE_URL to agent-service environment in docker-compose

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 05:45:08 -08:00
hailin 9a1ecf10ec fix: add restart policy, global error handlers, and fix tenant schema bug
- Add restart: unless-stopped to all 12 Docker services
- Add process.on(unhandledRejection/uncaughtException) to all 7 service main.ts
- Fix handleEventTrigger using tenantId UUID as schema name instead of slug lookup
- Wrap Redis event subscription callbacks in try/catch

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 05:30:34 -08:00
hailin 806113554b fix: remove AuthGuard('jwt') from all service controllers
Kong handles JWT validation at the gateway level. Service-level
AuthGuard('jwt') fails because services don't register a Passport
JWT strategy (only auth-service does). Removed from 17 controllers
across ops, inventory, monitor, comm, audit, and agent services.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 23:42:37 -08:00
hailin d8cb2a9c6f fix: use standard TypeORM repos and header-based tenant extraction
- Replace TenantAwareRepository with standard @InjectRepository
  (TenantAwareRepository requires AsyncLocalStorage tenant context
  middleware which agent-service does not have)
- Replace @TenantId() decorator with @Headers('x-tenant-id')
  for direct HTTP header extraction
- Return defaults gracefully when no tenant is selected

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 22:41:30 -08:00
hailin f897cfe240 fix: remove AuthGuard('jwt') from agent-service controllers
Agent-service does not have a registered Passport JWT strategy —
JWT validation is handled by Kong API gateway. The AuthGuard was
causing 500 "Unknown authentication strategy" errors on all
new controller endpoints.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 22:36:46 -08:00
hailin 5ee1227800 feat: add backend controllers for agent config, skills, and hooks
Implement missing REST API endpoints that the web-admin frontend
pages were calling but had no backend support:

- GET/POST/PUT /api/v1/agent-config (engine, prompt, turns, budget, tools)
- GET/POST/PUT/DELETE /api/v1/agent/skills (CRUD for agent skills)
- GET/POST/PUT/DELETE /api/v1/agent/hooks (CRUD for hook scripts)

Each endpoint includes entity, repository, service, and controller
layers following the existing DDD + tenant-aware patterns.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 22:26:25 -08:00
hailin 8b92abcce9 fix: handle undefined from eventQueue.shift() in SDK engine
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 22:10:43 -08:00
hailin c75ad27771 feat: add Claude Agent SDK engine with multi-tenant support
Add @anthropic-ai/claude-agent-sdk as a third engine (pure additive, no changes
to existing CLI/API engines). Includes full frontend admin page.

Backend (agent-service):
- ClaudeAgentSdkEngine: implements AgentEnginePort using SDK's query() API
- ApprovalGate: L2 tool approval with configurable auto-approve timeout (default 120s)
- TenantAgentConfig entity: per-tenant billing mode, encrypted API key, timeout, tool lists
- AllowedToolsResolverService: RBAC-based tool whitelist (admin/operator/viewer)
- TenantAgentConfigController: REST endpoints for admin config management
- Default subscription billing (operator's Claude login, no API key needed)
- Optional per-tenant API key with AES-256-GCM encryption

Frontend (web-admin):
- SDK Config page at /agent-config/sdk with billing, timeout, tool permissions
- Sidebar navigation entry under Agent Config
- React Query key for tenant SDK config

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 18:38:30 -08:00
hailin 00f8801d51 Initial commit: IT0 AI-powered server cluster operations platform
Full-stack monorepo with DDD + Clean Architecture:
- Backend: 7 NestJS microservices + 5 shared libraries (TypeScript)
- Mobile: Flutter app with Riverpod (Dart)
- Web Admin: Next.js dashboard with Zustand + React Query
- Voice: Python voice service (STT/TTS/VAD)
- Infra: Docker Compose, K8s manifests, Turborepo build

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 22:54:37 -08:00