Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 43 additions & 1 deletion src/browser/generated.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* Browser Structure Registry - Auto-generated
*
* Contains 11 daemons and 211 commands and 2 adapters and 28 widgets.
* Contains 11 daemons and 218 commands and 2 adapters and 28 widgets.
* Generated by scripts/generate-structure.ts - DO NOT EDIT MANUALLY
*/

Expand Down Expand Up @@ -163,6 +163,13 @@ import { LogsSearchBrowserCommand } from './../commands/logs/search/browser/Logs
import { LogsStatsBrowserCommand } from './../commands/logs/stats/browser/LogsStatsBrowserCommand';
import { MediaProcessBrowserCommand } from './../commands/media/process/browser/MediaProcessBrowserCommand';
import { MediaResizeBrowserCommand } from './../commands/media/resize/browser/MediaResizeBrowserCommand';
import { MigrationCutoverBrowserCommand } from './../commands/migration/cutover/browser/MigrationCutoverBrowserCommand';
import { MigrationPauseBrowserCommand } from './../commands/migration/pause/browser/MigrationPauseBrowserCommand';
import { MigrationResumeBrowserCommand } from './../commands/migration/resume/browser/MigrationResumeBrowserCommand';
import { MigrationRollbackBrowserCommand } from './../commands/migration/rollback/browser/MigrationRollbackBrowserCommand';
import { MigrationStartBrowserCommand } from './../commands/migration/start/browser/MigrationStartBrowserCommand';
import { MigrationStatusBrowserCommand } from './../commands/migration/status/browser/MigrationStatusBrowserCommand';
import { MigrationVerifyBrowserCommand } from './../commands/migration/verify/browser/MigrationVerifyBrowserCommand';
import { PersonaGenomeBrowserCommand } from './../commands/persona/genome/browser/PersonaGenomeBrowserCommand';
import { GenomeCaptureFeedbackBrowserCommand } from './../commands/persona/learning/capture-feedback/browser/GenomeCaptureFeedbackBrowserCommand';
import { GenomeCaptureInteractionBrowserCommand } from './../commands/persona/learning/capture-interaction/browser/GenomeCaptureInteractionBrowserCommand';
Expand Down Expand Up @@ -1053,6 +1060,41 @@ export const BROWSER_COMMANDS: CommandEntry[] = [
className: 'MediaResizeBrowserCommand',
commandClass: MediaResizeBrowserCommand
},
{
name: 'migration/cutover',
className: 'MigrationCutoverBrowserCommand',
commandClass: MigrationCutoverBrowserCommand
},
{
name: 'migration/pause',
className: 'MigrationPauseBrowserCommand',
commandClass: MigrationPauseBrowserCommand
},
{
name: 'migration/resume',
className: 'MigrationResumeBrowserCommand',
commandClass: MigrationResumeBrowserCommand
},
{
name: 'migration/rollback',
className: 'MigrationRollbackBrowserCommand',
commandClass: MigrationRollbackBrowserCommand
},
{
name: 'migration/start',
className: 'MigrationStartBrowserCommand',
commandClass: MigrationStartBrowserCommand
},
{
name: 'migration/status',
className: 'MigrationStatusBrowserCommand',
commandClass: MigrationStatusBrowserCommand
},
{
name: 'migration/verify',
className: 'MigrationVerifyBrowserCommand',
commandClass: MigrationVerifyBrowserCommand
},
{
name: 'persona/genome',
className: 'PersonaGenomeBrowserCommand',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ import { Events } from '@system/core/shared/Events';
import type { DataListParams, DataListResult } from '@commands/data/list/shared/DataListTypes';
import type { DataCreateParams, DataCreateResult } from '@commands/data/create/shared/DataCreateTypes';
import type { DataUpdateParams, DataUpdateResult } from '@commands/data/update/shared/DataUpdateTypes';
import { getVoiceOrchestrator } from '@system/voice/server/VoiceOrchestrator';
import { getVoiceOrchestrator } from '@system/voice/server';
import { LIVEKIT_URL, LIVEKIT_API_KEY, LIVEKIT_API_SECRET } from '@shared/AudioConstants';
import { getSecret } from '@system/secrets/SecretManager';

import { DataList } from '../../../../data/list/shared/DataListTypes';
import { DataCreate } from '../../../../data/create/shared/DataCreateTypes';
Expand All @@ -36,7 +38,9 @@ export class LiveJoinServerCommand extends LiveJoinCommand {
callId: '' as UUID,
existed: false,
participants: [],
myParticipant: null as any
myParticipant: null as any,
livekitToken: '',
livekitUrl: '',
});
}

Expand All @@ -50,7 +54,9 @@ export class LiveJoinServerCommand extends LiveJoinCommand {
callId: '' as UUID,
existed: false,
participants: [],
myParticipant: null as any
myParticipant: null as any,
livekitToken: '',
livekitUrl: '',
});
}

Expand All @@ -73,14 +79,16 @@ export class LiveJoinServerCommand extends LiveJoinCommand {
participant: myParticipant
});

// 7. Register with VoiceOrchestrator so AI participants can join the audio call
// This connects AI personas to the streaming-core call server
// 7. Register with VoiceOrchestrator — spawns STT listener in LiveKit room
const allParticipantIds = call.getActiveParticipants().map(p => p.userId);
try {
await getVoiceOrchestrator().registerSession(call.id, room.id, allParticipantIds);
} catch (error) {
console.warn('Failed to register voice session:', error);
}
await getVoiceOrchestrator().registerSession(call.id, room.id, allParticipantIds);

// 8. Generate LiveKit access token for WebRTC connection
const livekitToken = await this.generateLiveKitToken(
call.id,
user.id,
user.displayName
);

const result = {
success: true,
Expand All @@ -91,11 +99,13 @@ export class LiveJoinServerCommand extends LiveJoinCommand {
callId: call.id,
existed,
participants: call.getActiveParticipants(),
myParticipant
myParticipant,
livekitToken,
livekitUrl: getSecret('LIVEKIT_URL', 'LiveJoinServerCommand') || LIVEKIT_URL,
};

// DEBUG: Log what we're returning to browser
console.error(`🎙️ LiveJoin RESULT: callId=${call.id.slice(0, 8)}, existed=${existed}, participants=${call.getActiveParticipants().length}, myParticipant=${myParticipant.displayName}`);
console.error(`🎙️ LiveJoin RESULT: callId=${call.id.slice(0, 8)}, existed=${existed}, participants=${call.getActiveParticipants().length}, myParticipant=${myParticipant.displayName}, livekitToken=${livekitToken.slice(0, 20)}...`);
Copy link

Copilot AI Feb 21, 2026

Choose a reason for hiding this comment

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

Logging any portion of the LiveKit JWT access token is risky (it’s a bearer credential until expiry, and logs often end up in external systems). Remove token contents from logs (or log only the fact that a token was generated, plus maybe its TTL/length).

Copilot uses AI. Check for mistakes.

return transformPayload(params, result);
}
Expand Down Expand Up @@ -284,6 +294,38 @@ export class LiveJoinServerCommand extends LiveJoinCommand {
);
}

/**
* Generate a LiveKit JWT access token for a human participant.
* Includes ParticipantMetadata with role=human for typed classification.
* Uses livekit-server-sdk to create a token granting room join + publish + subscribe.
*/
private async generateLiveKitToken(
callId: string,
userId: string,
displayName: string
): Promise<string> {
const { AccessToken } = await import('livekit-server-sdk');
const { ParticipantRole } = await import('../../../../../shared/LiveKitTypes');

const apiKey = getSecret('LIVEKIT_API_KEY', 'LiveJoinServerCommand') || LIVEKIT_API_KEY;
const apiSecret = getSecret('LIVEKIT_API_SECRET', 'LiveJoinServerCommand') || LIVEKIT_API_SECRET;
const token = new AccessToken(apiKey, apiSecret, {
identity: userId,
name: displayName,
metadata: JSON.stringify({ role: ParticipantRole.Human }),
ttl: '6h',
});
token.addGrant({
room: callId,
roomJoin: true,
canPublish: true,
canSubscribe: true,
canPublishData: true,
});

return await token.toJwt();
}

/**
* Look up user info for a list of user IDs
*/
Expand Down
6 changes: 6 additions & 0 deletions src/commands/collaboration/live/join/shared/LiveJoinTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ export interface LiveJoinResult extends CommandResult {

/** The current user's participant entry */
myParticipant: CallParticipant;

/** LiveKit JWT access token for WebRTC connection */
livekitToken: string;

/** LiveKit server URL (ws://host:port) */
livekitUrl: string;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { Commands } from '@system/core/shared/Commands';
import { Events } from '@system/core/shared/Events';
import type { DataListParams, DataListResult } from '@commands/data/list/shared/DataListTypes';
import type { DataUpdateParams, DataUpdateResult } from '@commands/data/update/shared/DataUpdateTypes';
import { getVoiceOrchestrator } from '@system/voice/server/VoiceOrchestrator';
import { getVoiceOrchestrator } from '@system/voice/server';

import { DataList } from '../../../../data/list/shared/DataListTypes';
import { DataUpdate } from '../../../../data/update/shared/DataUpdateTypes';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import { CommandBase, type ICommandDaemon } from '@daemons/command-daemon/shared/CommandBase';
import type { JTAGContext } from '@system/core/types/JTAGTypes';
import { Events } from '@system/core/shared/Events';
// import { ValidationError } from '@system/core/types/ErrorTypes'; // Uncomment when adding validation
import { getRustVoiceOrchestrator } from '@system/voice/server';
import type { CollaborationLiveTranscriptionParams, CollaborationLiveTranscriptionResult } from '../shared/CollaborationLiveTranscriptionTypes';
import { createCollaborationLiveTranscriptionResultFromParams } from '../shared/CollaborationLiveTranscriptionTypes';

Expand All @@ -18,13 +18,9 @@ export class CollaborationLiveTranscriptionServerCommand extends CommandBase<Col
}

async execute(params: CollaborationLiveTranscriptionParams): Promise<CollaborationLiveTranscriptionResult> {
console.log(`[STEP 10] 🎙️ SERVER: Relaying transcription to VoiceOrchestrator: "${params.transcript.slice(0, 50)}..."`);

// Emit the voice:transcription event on the SERVER Events bus
// This allows VoiceOrchestrator (server-side) to receive the transcription
// Use callSessionId (the call UUID) so VoiceOrchestrator can look up the session context
// Emit event for any subscribers (VoiceOrchestrator TS subscribes to this)
Events.emit('voice:transcription', {
sessionId: params.callSessionId, // Call session UUID
sessionId: params.callSessionId,
speakerId: params.speakerId,
speakerName: params.speakerName,
transcript: params.transcript,
Expand All @@ -33,12 +29,33 @@ export class CollaborationLiveTranscriptionServerCommand extends CommandBase<Col
timestamp: params.timestamp
});

console.log(`[STEP 10] ✅ Transcription event emitted on server Events bus`);
// Route through Rust VoiceOrchestrator for AI participant notification
const responderIds = await getRustVoiceOrchestrator().onUtterance({
sessionId: params.callSessionId,
speakerId: params.speakerId,
speakerName: params.speakerName,
speakerType: 'human',
transcript: params.transcript,
confidence: params.confidence,
timestamp: params.timestamp,
});
Comment on lines +32 to +41
Copy link

Copilot AI Feb 21, 2026

Choose a reason for hiding this comment

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

This code path unconditionally routes via getRustVoiceOrchestrator(), which bypasses the USE_RUST_VOICE feature flag exposed by getVoiceOrchestrator(). If the intent is to respect that flag, route through getVoiceOrchestrator().onUtterance(...) (or conditionally call Rust vs TS).

Copilot uses AI. Check for mistakes.

// Emit directed events to each AI responder
for (const targetId of responderIds) {
Events.emit('voice:transcription:directed', {
sessionId: params.callSessionId,
speakerId: params.speakerId,
speakerName: params.speakerName,
transcript: params.transcript,
confidence: params.confidence,
timestamp: params.timestamp,
targetPersonaId: targetId,
});
}

// Return successful result
return createCollaborationLiveTranscriptionResultFromParams(params, {
success: true,
message: `Transcription relayed to VoiceOrchestrator: "${params.transcript.slice(0, 30)}..."`
message: `Transcription → ${responderIds.length} AI responders`
});
}
}
9 changes: 6 additions & 3 deletions src/commands/data/list/server/DataListServerCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export class DataListServerCommand<T extends BaseEntity> extends CommandBase<Dat
// causing massive lock contention when multiple personas responded concurrently.
// PersonaTimeline and other per-persona data structures pass their own dbHandle.

let countResult;
let countResult: { success: boolean; data?: number } | undefined;
let result;

// Build queries
Expand Down Expand Up @@ -91,10 +91,13 @@ export class DataListServerCommand<T extends BaseEntity> extends CommandBase<Dat
}

// Use ORM for all operations (routes to Rust with correct dbPath)
countResult = await ORM.count(countQuery, dbPath);
// skipCount avoids a separate COUNT(*) round-trip when the caller only needs items
if (!params.skipCount) {
countResult = await ORM.count(countQuery, dbPath);
}
result = await ORM.query<BaseEntity>(storageQuery, dbPath);

const totalCount = countResult.success ? (countResult.data ?? 0) : 0;
const totalCount = countResult?.success ? (countResult.data ?? 0) : 0;

if (!result.success) {
const errorMsg = result.error || 'Unknown DataDaemon error';
Expand Down
3 changes: 3 additions & 0 deletions src/commands/data/list/shared/DataListTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ export interface DataListParams extends CommandParams {
readonly verbose?: boolean;
/** Database handle for multi-database operations */
readonly dbHandle?: DbHandle;
/** Skip the separate COUNT query (saves one IPC round-trip per call).
* When true, result.count will be 0. Use when you only need the items. */
readonly skipCount?: boolean;
/**
* Backend preference for browser commands:
* - 'server': Always fetch from server (use for real-time data)
Expand Down
20 changes: 20 additions & 0 deletions src/commands/migration/cutover/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Development files
.eslintrc*
tsconfig*.json
vitest.config.ts

# Build artifacts
*.js.map
*.d.ts.map

# IDE
.vscode/
.idea/

# Logs
*.log
npm-debug.log*

# OS files
.DS_Store
Thumbs.db
Loading
Loading