Skip to content

Commit 5a438bb

Browse files
joelteplyclaude
andauthored
Feature/client daemon refactoring (#256)
* fix significant widget and daemon bottlenecks * persona fixes * Add concurrent inference worker pool (4 parallel model instances) - Create worker_pool.rs with multi-instance model loading - Each worker has own QuantizedModelState + Metal GPU device - Request channel distributes work via tokio mpsc - Semaphore tracks available workers for backpressure - Auto-detect worker count based on system memory (~2GB per worker) - Update InferenceCoordinator: maxConcurrent 1→3, reduced cooldowns - Fallback to single BF16 instance when LoRA adapters requested Before: 1 request/~6s + 30s timeout cascade After: 4 requests/~6s in parallel, no timeouts Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Add DaemonOrchestrator for wave-based parallel startup - Daemons start in dependency order: critical → integration → lightweight - Critical path (data, command, events, session): 350ms max - Integration daemons wait for DataDaemon before starting - Lightweight daemons (health, widget, logger) start immediately - Phase breakdown metrics logged for observability Phases: - critical: 4 daemons, max=207ms (UI can render) - integration: 7 daemons, max=3518ms (AIProvider bottleneck) - lightweight: 7 daemons, max=130ms Total startup: 3531ms (critical path ready much sooner) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Fix browser launch logic and startup reliability - Fix SystemOrchestrator navigate command (remove invalid --path param) - Fix launch-and-capture.ts: check ping, refresh if connected, open if not - Fix SystemMetricsCollector countCommands to use ping instead of browser logs - Add deterministic UUIDs for seeded users (Joel, Claude Code) - Improve UserDaemonServer error logging for persona client creation Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Fix browser launch and UUID generation Browser launch: - ALWAYS open browser window, don't just reload - Ensures user sees something even if WebSocket connected but window closed - Both SystemOrchestrator and launch-and-capture now open browser unconditionally UUID fix: - stringToUUID now generates valid 36-char UUIDs (was generating 32-char) - Last segment now correctly 12 chars instead of 8 ChatWidget: - Use server backend for $in queries (localStorage doesn't support $in) - Add debug logging for member loading Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Add backend:server support for browser data commands - DataReadBrowserCommand now supports backend:'server' to bypass localStorage - ChatWidget uses server backend for room queries to avoid stale cache - Fixes issues with members not loading due to localStorage not supporting $in Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Consolidate identifier resolution + encode compression ethos CONSOLIDATION (Ministry of Code Deletion): - RoutingService is now THE single source of truth for room/user resolution - Added resolveRoomIdentifier() and resolveUserIdentifier() convenience functions - Added name fallback query for legacy support - Migrated ChatSendServerCommand: deleted findRoom(), uses RoutingService - Migrated ChatAnalyzeServerCommand: deleted resolveRoom(), uses RoutingService - Migrated ChatPollServerCommand: deleted inline resolution, uses RoutingService - WallTypes.isRoomUUID() now delegates to RoutingService.isUUID() - MainWidget: deleted dead handleTabClick/handleTabClose, simplified openContentTab ETHOS (CLAUDE.md): - Added "The Compression Principle" - one logical decision, one place - Added "The Methodical Process" - 8 mandatory steps, outlier validation - Encoded the Ministry philosophy: deletion without loss = compression = efficiency Net change: +306/-298 lines (compression-neutral while adding documentation) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Fix UUID corruption and contentState persistence issues Key fixes: - data-clear.ts: Clear session metadata during reseed to prevent stale entityIds from persisting (root cause of corrupted UUID bug) - MainWidget.ts: Add userId setup with retry in openContentFromUrl() and initializeContentTabs() to ensure ContentService can persist to database - RoutingService.ts: Fix example UUID in comment - SchemaBasedFactory.ts: Fix hardcoded test UUID The corrupted UUID issue (5e71a0c8-0303-4eb8-a478-3a121248) was caused by stale session metadata files that weren't cleared during data reseed. The session files stored old entityIds that no longer existed after reseeding the database. ContentState persistence now works - tabs are saved to database with correct UUIDs. Tab restore on refresh still needs investigation due to session management timing issues. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Fix tab restoration by bypassing stale localStorage cache Root cause: Browser's OfflineStorageAdapter caches user_states in localStorage. When tabs are opened, the server database is updated, but localStorage retains stale data. On page refresh, loadUserContext() would get old cached data with fewer/no openItems. Fix: Add `backend: 'server'` to user_states query in loadUserContext() to bypass localStorage cache and always fetch fresh contentState from the server database. Also added debug logging (temporary) to help diagnose initialization timing issues between loadUserContext() and initializeContentTabs(). Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * cleanup scripts * Fix Clippy warnings in Rust workers - inference-grpc: Fix dead code, use pool stats, proper strip_prefix - data-daemon: Fix HDD acronym, add type alias for complex type - inference: Collapse nested if-let - model.rs: Use struct literal instead of removed new() Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Fix AI persona death and Clippy duplicate_mod warning Root cause: UserDaemon's initializeDeferred() tried to create persona clients before JTAGSystem.daemons was populated. DataDaemon emits system:ready during its initialize() phase, triggering UserDaemon's ensurePersonaClients() which needs CommandDaemon. But CommandDaemon wasn't yet registered to JTAGSystem.daemons (only happens AFTER orchestrator.startAll() returns). Fix: 1. CommandDaemonServer now registers itself to globalThis during its initialize() phase, providing early access for other daemons 2. JTAGSystem.getCommandsInterface() now checks globalThis first, falling back to this.daemons for compatibility Also fixed Clippy duplicate_mod warning in training-worker: - logger_client.rs now re-exports JTAG protocol types - messages.rs uses re-exports instead of including jtag_protocol directly Verified: All 11 AI personas now healthy and responding to messages. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * reliabiity issues resolved. * more reliable models * Verify daemon refinements working - Per-persona inference logging confirmed (Helper AI, Teacher AI, etc.) - System utilities correctly show [unknown] in Rust logs - AI responses verified working via Candle gRPC and cloud APIs - Version bump 1.0.7184 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Fix inference bottlenecks: SignalDetector + prompt truncation 1. SignalDetector: Switch from local (slow) to Groq (fast) - Was flooding local inference queue with classification calls - Groq responds in <1s vs local ~10s - Frees local queue for actual persona responses 2. CandleGrpcAdapter: Add prompt truncation (24K char limit) - Prevents "narrow invalid args" tensor dimension errors - Large RAG contexts were sending 74000+ char prompts - Model has 8K token (~32K char) context window - Truncation preserves system prompt + recent messages Before: Constant queue backlog, tensor errors, hangs After: Workers have availability, no tensor errors, faster responses Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Fix chat scroll latch: use latch state instead of fixed threshold The chat widget was unlatching from the bottom when large messages arrived because the fixed 200px threshold was too small. Changes: - Add isLatchedToBottom state to track user intent - Dynamic threshold: max of config, 50% viewport, or 500px - ResizeObserver checks latch state instead of distance - Scroll handler updates latch with tighter 100px threshold - Scroll listener active when autoScroll enabled Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Fix initial scroll to bottom on fresh chat load The scrollToEnd was called immediately after adding items to DOM, but the browser hadn't laid them out yet. Using double-rAF ensures the DOM is fully rendered before scrolling. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * audit fix --------- Co-authored-by: Joel <undefined> Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 7add4c7 commit 5a438bb

File tree

144 files changed

+3182
-8687
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

144 files changed

+3182
-8687
lines changed

CLAUDE.md

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,97 @@ Events.emit('data:users:created', newUser);
3131

3232
---
3333

34+
## 🧬 THE COMPRESSION PRINCIPLE (Fundamental Law)
35+
36+
**One logical decision, one place. No exceptions.**
37+
38+
This applies to BOTH program memory and data memory:
39+
40+
| Type | Uncompressed (BAD) | Compressed (GOOD) |
41+
|------|-------------------|-------------------|
42+
| **Logic** | `findRoom()` in 5 files | `resolveRoomIdentifier()` in RoutingService |
43+
| **Data** | `UUID_PATTERN` in 3 files | `isUUID()` exported from one place |
44+
| **Constants** | Magic strings everywhere | `ROOM_UNIQUE_IDS.GENERAL` |
45+
46+
**The ideal codebase is maximally compressed:**
47+
```
48+
Root Primitives (minimal) ← Commands.execute(), Events.emit()
49+
50+
Derived Abstractions ← RoutingService, ContentService
51+
52+
Application Code ← References, never reimplements
53+
```
54+
55+
**Why this matters:**
56+
- Duplication = redundancy = **drift** (copies diverge over time = bugs)
57+
- Compression = elegance = **coherence** (one truth = consistency)
58+
- The most elegant equation is the most minimal: **E = mc²**
59+
60+
**The test:** For ANY decision (logic or data), can you point to exactly ONE place in the codebase? If not, you have uncompressed redundancy that WILL cause bugs.
61+
62+
**The goal:** Build from root primitives. Let elegant architecture emerge from compression. When abstractions are right, code reads like intent.
63+
64+
---
65+
66+
## 🔬 THE METHODICAL PROCESS (Building With Intent)
67+
68+
**Be SUPER methodical. No skipping steps. This is the discipline that makes elegance real.**
69+
70+
### The Outlier Validation Strategy
71+
72+
Don't build exhaustively. Don't build hopefully. **Build diversely to prove the interface:**
73+
74+
```
75+
Wrong: Build adapters 1, 2, 3, 4, 5... (exhaustive - wastes time)
76+
Wrong: Build adapter 1, assume 2-5 work (hopeful - will break)
77+
Right: Build adapter 1 (local/simple) + adapter N (most different)
78+
If both fit cleanly → interface is proven → rest are trivial
79+
```
80+
81+
**Example - AI Provider Adapters:**
82+
1. Build Ollama adapter (local, unique API)
83+
2. Build cloud adapter (remote, auth, rate limits)
84+
3. Try LoRA fine-tuning on each
85+
4. If interface handles both extremes → it handles everything
86+
87+
This is like testing edge cases: if edges pass, middle is guaranteed.
88+
89+
### The Mandatory Steps
90+
91+
For ANY new pattern or abstraction:
92+
93+
```
94+
1. IDENTIFY - See the pattern emerging (2-3 similar implementations)
95+
2. DESIGN - Draft the interface/abstraction
96+
3. OUTLIER A - Build first implementation (pick something local/simple)
97+
4. OUTLIER B - Build second implementation (pick something maximally DIFFERENT)
98+
5. VALIDATE - Does the interface fit both WITHOUT forcing? If no, redesign.
99+
6. GENERATOR - Write generator to encode the pattern
100+
7. DOCUMENT - Update README, add to CLAUDE.md if architectural
101+
8. STOP - Don't build remaining implementations until needed
102+
```
103+
104+
**NEVER skip steps 4-6.** Step 4 (outlier B) catches bad abstractions early. Step 5 (validate) prevents wishful thinking. Step 6 (generator) ensures the pattern is followed forever.
105+
106+
### Building With Intent (Not Over-Engineering)
107+
108+
```
109+
Over-engineering: Build the future NOW (10 adapters day 1)
110+
Under-engineering: Build only NOW, refactor "later" (never happens)
111+
Intent: Build NOW in a shape that WELCOMES the future
112+
```
113+
114+
**The "first adapter that seems silly"** - it's not silly. It's laying rails. When adapter 2 comes, it slots in. When adapter 3 comes, you realize the interface was right. The first adapter was a TEST of your idealized future.
115+
116+
It's OK to:
117+
- Build one adapter even if the pattern seems overkill
118+
- Design the interface as if 10 implementations exist
119+
- Add a TODO noting the intended extension point
120+
121+
**The restraint:** See the next few moves like chess, but don't PLAY them all now. Lay the pattern, validate with outliers, write the generator, stop.
122+
123+
---
124+
34125
## 🎯 CORE PHILOSOPHY: Continuous Improvement
35126

36127
**"A good developer improves the entire system continuously, not just their own new stuff."**

src/debug/jtag/api/data-seed/UserDataSeed.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { PersonaUser, type PersonaUserData } from '../../domain/user/PersonaUser
1111
import { SystemUser } from '../../domain/user/SystemUser';
1212
import { BaseUser } from '../../domain/user/BaseUser';
1313
import { generateUUID, type UUID } from '../../system/core/types/CrossPlatformUUID';
14+
import { DEFAULT_USERS } from '../../system/data/domains/DefaultEntities';
1415
import type { BaseUserDataWithRelationships } from '../../domain/user/UserRelationships';
1516
import { USER_IDS, USER_CONFIG, COLLECTIONS } from './SeedConstants';
1617
import { MODEL_IDS } from '../../system/shared/Constants';
@@ -39,8 +40,9 @@ export class UserDataSeed {
3940
*/
4041
public static generateSeedUsers(): UserSeedData {
4142
// Create human user with proper domain object
43+
// Use DEFAULT_USERS.HUMAN constant directly for single source of truth
4244
const humanUserData: HumanUserData = {
43-
userId: generateUUID(),
45+
userId: DEFAULT_USERS.HUMAN,
4446
sessionId: generateUUID(),
4547
displayName: USER_CONFIG.HUMAN.DISPLAY_NAME || USER_CONFIG.HUMAN.NAME,
4648
citizenType: 'human',
@@ -70,7 +72,7 @@ export class UserDataSeed {
7072

7173
// Claude Code - AI Assistant Agent
7274
const claudeAgentData: AgentUserData = {
73-
userId: generateUUID(),
75+
userId: DEFAULT_USERS.CLAUDE_CODE,
7476
sessionId: generateUUID(),
7577
displayName: USER_CONFIG.CLAUDE.NAME,
7678
citizenType: 'ai',
@@ -97,7 +99,7 @@ export class UserDataSeed {
9799

98100
// GeneralAI - General Assistant Persona
99101
const generalPersonaData: PersonaUserData = {
100-
userId: generateUUID(),
102+
userId: DEFAULT_USERS.GENERAL_AI,
101103
sessionId: generateUUID(),
102104
displayName: 'GeneralAI',
103105
citizenType: 'ai',
@@ -132,7 +134,7 @@ export class UserDataSeed {
132134

133135
// CodeAI - Code Analysis Specialist Agent
134136
const codeAgentData: AgentUserData = {
135-
userId: generateUUID(),
137+
userId: DEFAULT_USERS.CODE_AI,
136138
sessionId: generateUUID(),
137139
displayName: 'CodeAI',
138140
citizenType: 'ai',
@@ -161,7 +163,7 @@ export class UserDataSeed {
161163

162164
// PlannerAI - Strategic Planning Assistant Agent
163165
const plannerAgentData: AgentUserData = {
164-
userId: generateUUID(),
166+
userId: DEFAULT_USERS.PLANNER_AI,
165167
sessionId: generateUUID(),
166168
displayName: 'PlannerAI',
167169
citizenType: 'ai',
@@ -190,7 +192,7 @@ export class UserDataSeed {
190192

191193
// Auto Route - Smart Agent Selection Agent
192194
const autoRouteAgentData: AgentUserData = {
193-
userId: generateUUID(),
195+
userId: DEFAULT_USERS.AUTO_ROUTE,
194196
sessionId: generateUUID(),
195197
displayName: 'Auto Route',
196198
citizenType: 'ai',

src/debug/jtag/commands/collaboration/chat/analyze/server/ChatAnalyzeServerCommand.ts

Lines changed: 18 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,8 @@ import type {
1111
} from '../shared/ChatAnalyzeTypes';
1212
import type { DataListParams, DataListResult } from '@commands/data/list/shared/DataListTypes';
1313
import { ChatMessageEntity } from '@system/data/entities/ChatMessageEntity';
14-
import { RoomEntity } from '@system/data/entities/RoomEntity';
15-
import type { UUID } from '@system/core/types/CrossPlatformUUID';
1614
import crypto from 'crypto';
15+
import { resolveRoomIdentifier } from '@system/routing/RoutingService';
1716

1817
export class ChatAnalyzeServerCommand extends ChatAnalyzeCommand {
1918

@@ -24,8 +23,23 @@ export class ChatAnalyzeServerCommand extends ChatAnalyzeCommand {
2423
protected async executeChatAnalyze(params: ChatAnalyzeParams): Promise<ChatAnalyzeResult> {
2524
const { roomId, checkDuplicates = true, checkTimestamps = true, limit = 500 } = params;
2625

27-
// Resolve room name to UUID if needed
28-
const resolvedRoomId = await this.resolveRoom(roomId, params);
26+
// Resolve room name to UUID (single source of truth: RoutingService)
27+
const resolved = await resolveRoomIdentifier(roomId);
28+
if (!resolved) {
29+
return {
30+
success: false,
31+
roomId,
32+
totalMessages: 0,
33+
analysis: {
34+
hasDuplicates: false,
35+
duplicateCount: 0,
36+
hasTimestampIssues: false,
37+
anomalyCount: 0,
38+
},
39+
error: `Room not found: ${roomId}`,
40+
} as ChatAnalyzeResult;
41+
}
42+
const resolvedRoomId = resolved.id;
2943

3044
// Get all messages from room
3145
const listResult = await Commands.execute<DataListParams, DataListResult<ChatMessageEntity>>(
@@ -182,43 +196,4 @@ export class ChatAnalyzeServerCommand extends ChatAnalyzeCommand {
182196

183197
return anomalies;
184198
}
185-
186-
/**
187-
* Resolve room name or ID to actual UUID
188-
* Accepts either a room name (e.g., "general") or a room UUID
189-
*/
190-
private async resolveRoom(roomIdOrName: string, params: ChatAnalyzeParams): Promise<UUID> {
191-
// Check if it already looks like a UUID
192-
const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
193-
if (uuidPattern.test(roomIdOrName)) {
194-
return roomIdOrName as UUID;
195-
}
196-
197-
// Query all rooms to find by name
198-
const result = await Commands.execute<DataListParams, DataListResult<RoomEntity>>(
199-
DATA_COMMANDS.LIST,
200-
{
201-
collection: RoomEntity.collection,
202-
filter: {},
203-
context: params.context,
204-
sessionId: params.sessionId
205-
}
206-
);
207-
208-
if (!result.success || !result.items) {
209-
throw new Error('Failed to query rooms');
210-
}
211-
212-
// Find by ID or name
213-
const room = result.items.find((r: RoomEntity) =>
214-
r.id === roomIdOrName || r.name === roomIdOrName
215-
);
216-
217-
if (!room) {
218-
const roomNames = result.items.map((r: RoomEntity) => r.name).join(', ');
219-
throw new Error(`Room not found: ${roomIdOrName}. Available rooms: ${roomNames}`);
220-
}
221-
222-
return room.id;
223-
}
224199
}

src/debug/jtag/commands/collaboration/chat/poll/server/ChatPollServerCommand.ts

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ import { ChatPollCommand } from '../shared/ChatPollCommand';
88
import type { ChatPollParams, ChatPollResult } from '../shared/ChatPollTypes';
99
import { DataDaemon } from '@daemons/data-daemon/shared/DataDaemon';
1010
import type { ChatMessageEntity } from '@system/data/entities/ChatMessageEntity';
11-
import type { RoomEntity } from '@system/data/entities/RoomEntity';
1211
import type { UUID } from '@system/core/types/CrossPlatformUUID';
12+
import { resolveRoomIdentifier } from '@system/routing/RoutingService';
1313

1414
export class ChatPollServerCommand extends ChatPollCommand {
1515

@@ -19,18 +19,13 @@ export class ChatPollServerCommand extends ChatPollCommand {
1919

2020
protected async executeChatPoll(params: ChatPollParams): Promise<ChatPollResult> {
2121
try {
22-
// Resolve room name to roomId if provided
22+
// Resolve room identifier (single source of truth: RoutingService)
2323
let roomId: UUID | undefined = params.roomId;
2424

2525
if (params.room && !roomId) {
26-
const roomsResult = await DataDaemon.query<RoomEntity>({
27-
collection: 'rooms',
28-
filter: { name: params.room },
29-
limit: 1
30-
});
31-
32-
if (roomsResult.success && roomsResult.data && roomsResult.data.length > 0) {
33-
roomId = roomsResult.data[0].id;
26+
const resolved = await resolveRoomIdentifier(params.room);
27+
if (resolved) {
28+
roomId = resolved.id;
3429
}
3530
}
3631

src/debug/jtag/commands/collaboration/chat/send/server/ChatSendServerCommand.ts

Lines changed: 11 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,14 @@ import { transformPayload } from '@system/core/types/JTAGTypes';
99
import type { ICommandDaemon } from '@daemons/command-daemon/shared/CommandBase';
1010
import { ChatSendCommand } from '../shared/ChatSendCommand';
1111
import type { ChatSendParams, ChatSendResult } from '../shared/ChatSendTypes';
12-
import { RoomEntity } from '@system/data/entities/RoomEntity';
1312
import { UserEntity } from '@system/data/entities/UserEntity';
1413
import { ChatMessageEntity, type MediaItem } from '@system/data/entities/ChatMessageEntity';
1514
import type { UUID } from '@system/core/types/CrossPlatformUUID';
1615
import { Commands } from '@system/core/shared/Commands';
1716
import type { DataListParams, DataListResult } from '@commands/data/list/shared/DataListTypes';
1817
import type { DataCreateParams, DataCreateResult } from '@commands/data/create/shared/DataCreateTypes';
1918
import { UserIdentityResolver } from '@system/user/shared/UserIdentityResolver';
19+
import { resolveRoomIdentifier } from '@system/routing/RoutingService';
2020

2121
export class ChatSendServerCommand extends ChatSendCommand {
2222

@@ -27,9 +27,12 @@ export class ChatSendServerCommand extends ChatSendCommand {
2727
protected async executeChatSend(params: ChatSendParams): Promise<ChatSendResult> {
2828
console.log('🔧 ChatSendServerCommand.executeChatSend START', { room: params.room });
2929

30-
// 1. Find room
31-
const room = await this.findRoom(params.room || 'general', params);
32-
console.log('🔧 ChatSendServerCommand.executeChatSend ROOM FOUND', { roomId: room.id, roomName: room.entity.name });
30+
// 1. Find room (single source of truth: RoutingService)
31+
const resolved = await resolveRoomIdentifier(params.room || 'general');
32+
if (!resolved) {
33+
throw new Error(`Room not found: ${params.room || 'general'}`);
34+
}
35+
console.log('🔧 ChatSendServerCommand.executeChatSend ROOM FOUND', { roomId: resolved.id, roomName: resolved.displayName });
3336

3437
// 2. Get sender - auto-detect caller identity (Claude Code, Joel, etc.)
3538
const sender = params.senderId
@@ -39,7 +42,7 @@ export class ChatSendServerCommand extends ChatSendCommand {
3942

4043
// 3. Create message entity
4144
const messageEntity = new ChatMessageEntity();
42-
messageEntity.roomId = room.id; // Use DataRecord.id, not entity.id
45+
messageEntity.roomId = resolved.id; // From RoutingService resolution
4346
messageEntity.senderId = sender.id; // sender is also DataRecord with .id
4447
console.log('🔧 ChatSendServerCommand.executeChatSend MESSAGE ENTITY', { roomId: messageEntity.roomId, senderId: messageEntity.senderId });
4548
messageEntity.senderName = sender.entity.displayName;
@@ -108,58 +111,17 @@ export class ChatSendServerCommand extends ChatSendCommand {
108111
// 5. Generate short ID (last 6 chars of UUID - from BaseEntity.id)
109112
const shortId = storedEntity.id.slice(-6);
110113

111-
console.log(`✅ Message sent: #${shortId} to ${room.entity.name}`);
114+
console.log(`✅ Message sent: #${shortId} to ${resolved.displayName}`);
112115

113116
return transformPayload(params, {
114117
success: true,
115-
message: `Message sent to ${room.entity.name} (#${shortId})`,
118+
message: `Message sent to ${resolved.displayName} (#${shortId})`,
116119
messageEntity: storedEntity,
117120
shortId: shortId,
118-
roomId: room.id
121+
roomId: resolved.id
119122
});
120123
}
121124

122-
/**
123-
* Find room by ID or name
124-
*/
125-
private async findRoom(roomIdOrName: string, params: ChatSendParams): Promise<{ id: UUID; entity: RoomEntity }> {
126-
console.log('🔧 findRoom START', { roomIdOrName });
127-
128-
// Query all rooms using data/list command
129-
const result = await Commands.execute<DataListParams, DataListResult<RoomEntity>>(
130-
DATA_COMMANDS.LIST,
131-
{
132-
collection: RoomEntity.collection,
133-
filter: {},
134-
context: params.context,
135-
sessionId: params.sessionId
136-
}
137-
);
138-
139-
console.log('🔧 findRoom QUERY RESULT', {
140-
success: result.success,
141-
recordCount: result.items?.length || 0
142-
});
143-
144-
if (!result.success || !result.items) {
145-
throw new Error('Failed to query rooms');
146-
}
147-
148-
// Find by ID or name
149-
const room = result.items.find((r: RoomEntity) =>
150-
r.id === roomIdOrName || r.name === roomIdOrName
151-
);
152-
153-
if (!room) {
154-
const roomNames = result.items.map((r: RoomEntity) => r.name).join(', ');
155-
console.log('🔧 findRoom NOT FOUND', { roomIdOrName, availableRooms: roomNames });
156-
throw new Error(`Room not found: ${roomIdOrName}. Available: ${roomNames}`);
157-
}
158-
159-
console.log('🔧 findRoom FOUND', { roomId: room.id, roomName: room.name });
160-
return { id: room.id, entity: room };
161-
}
162-
163125
/**
164126
* Find user by ID
165127
*/

0 commit comments

Comments
 (0)