newo 1.8.0 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,42 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.9.0] - 2025-09-16
9
+
10
+ ### Added
11
+ - **User Conversations Pull Functionality**: Complete conversation history extraction
12
+ - New `newo conversations` command to download user conversations and personas
13
+ - Multi-customer conversation support with `--customer <idn>` flag
14
+ - Chat History API integration (`/api/v1/chat/history`) with fallback to conversations acts API
15
+ - Automatic phone number extraction from persona actors
16
+ - Comprehensive pagination handling for large conversation datasets
17
+ - Clean YAML output format in `newo_customers/{customerIdn}/conversations.yaml`
18
+
19
+ ### Enhanced
20
+ - **Conversation Data Processing**: Optimized structure and chronological ordering
21
+ - Acts sorted by datetime ascending (chronological conversation flow)
22
+ - Personas sorted by most recent activity (descending)
23
+ - Redundant fields removed (`is_agent`, `session_id: unknown`, etc.)
24
+ - Clean persona structure: `id` → `name` → `phone` → `act_count` → `acts`
25
+ - Proper datetime extraction from chat history API responses
26
+
27
+ ### Technical
28
+ - **New API Functions**: Type-safe conversation API integration
29
+ - `listUserPersonas()` - Get all user personas with pagination
30
+ - `getChatHistory()` - Get conversation history for user actors
31
+ - `getConversationActs()` - Fallback for accounts with proper permissions
32
+ - `pullConversations()` - Complete conversation sync orchestration
33
+ - **NPM Scripts**: Added convenient conversation commands
34
+ - `npm run conversations` - Build and pull conversations
35
+ - `npm run conversations:all` - Legacy alias for compatibility
36
+
37
+ ### Performance
38
+ - **Concurrent Processing**: Efficient conversation data extraction
39
+ - Parallel API calls with concurrency limiting (p-limit)
40
+ - Graceful error handling with persona-level fault tolerance
41
+ - No artificial limits on personas or acts (loads all available data)
42
+ - Multi-customer support with authentication reuse
43
+
8
44
  ## [1.8.0] - 2025-09-15
9
45
 
10
46
  ### Added
package/dist/api.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { type AxiosInstance } from 'axios';
2
- import type { ProjectMeta, Agent, Skill, FlowEvent, FlowState, AkbImportArticle, CustomerProfile, CustomerAttribute, CustomerAttributesResponse } from './types.js';
2
+ import type { ProjectMeta, Agent, Skill, FlowEvent, FlowState, AkbImportArticle, CustomerProfile, CustomerAttribute, CustomerAttributesResponse, UserPersonaResponse, UserPersona, ConversationActsResponse, ConversationActsParams, ChatHistoryParams, ChatHistoryResponse } from './types.js';
3
3
  export declare function makeClient(verbose?: boolean, token?: string): Promise<AxiosInstance>;
4
4
  export declare function listProjects(client: AxiosInstance): Promise<ProjectMeta[]>;
5
5
  export declare function listAgents(client: AxiosInstance, projectId: string): Promise<Agent[]>;
@@ -13,4 +13,12 @@ export declare function importAkbArticle(client: AxiosInstance, articleData: Akb
13
13
  export declare function getCustomerProfile(client: AxiosInstance): Promise<CustomerProfile>;
14
14
  export declare function getCustomerAttributes(client: AxiosInstance, includeHidden?: boolean): Promise<CustomerAttributesResponse>;
15
15
  export declare function updateCustomerAttribute(client: AxiosInstance, attribute: CustomerAttribute): Promise<void>;
16
+ export declare function listUserPersonas(client: AxiosInstance, page?: number, per?: number): Promise<UserPersonaResponse>;
17
+ export declare function getUserPersona(client: AxiosInstance, personaId: string): Promise<UserPersona>;
18
+ export declare function getAccount(client: AxiosInstance): Promise<{
19
+ id: string;
20
+ [key: string]: any;
21
+ }>;
22
+ export declare function getConversationActs(client: AxiosInstance, params: ConversationActsParams): Promise<ConversationActsResponse>;
23
+ export declare function getChatHistory(client: AxiosInstance, params: ChatHistoryParams): Promise<ChatHistoryResponse>;
16
24
  //# sourceMappingURL=api.d.ts.map
package/dist/api.js CHANGED
@@ -121,4 +121,54 @@ export async function updateCustomerAttribute(client, attribute) {
121
121
  value_type: attribute.value_type
122
122
  });
123
123
  }
124
+ // Conversation API Functions
125
+ export async function listUserPersonas(client, page = 1, per = 50) {
126
+ const response = await client.get('/api/v1/bff/conversations/user-personas', {
127
+ params: { page, per }
128
+ });
129
+ return response.data;
130
+ }
131
+ export async function getUserPersona(client, personaId) {
132
+ const response = await client.get(`/api/v1/bff/conversations/user-personas/${personaId}`);
133
+ return response.data;
134
+ }
135
+ export async function getAccount(client) {
136
+ const response = await client.get('/api/v1/account');
137
+ return response.data;
138
+ }
139
+ export async function getConversationActs(client, params) {
140
+ const queryParams = {
141
+ user_persona_id: params.user_persona_id,
142
+ page: params.page || 1,
143
+ per: params.per || 50
144
+ };
145
+ // Only add optional parameters if explicitly provided
146
+ if (params.turn_type)
147
+ queryParams.turn_type = params.turn_type;
148
+ if (params.connectors)
149
+ queryParams.connectors = params.connectors;
150
+ if (params.from_date)
151
+ queryParams.from_date = params.from_date;
152
+ if (params.to_date)
153
+ queryParams.to_date = params.to_date;
154
+ const response = await client.get('/api/v1/bff/conversations/acts', {
155
+ params: queryParams
156
+ });
157
+ return response.data;
158
+ }
159
+ export async function getChatHistory(client, params) {
160
+ const queryParams = {
161
+ user_actor_id: params.user_actor_id,
162
+ page: params.page || 1,
163
+ per: params.per || 50
164
+ };
165
+ // Only add agent_actor_id if provided
166
+ if (params.agent_actor_id) {
167
+ queryParams.agent_actor_id = params.agent_actor_id;
168
+ }
169
+ const response = await client.get('/api/v1/chat/history', {
170
+ params: queryParams
171
+ });
172
+ return response.data;
173
+ }
124
174
  //# sourceMappingURL=api.js.map
package/dist/cli.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import minimist from 'minimist';
3
3
  import dotenv from 'dotenv';
4
4
  import { makeClient, getProjectMeta, importAkbArticle } from './api.js';
5
- import { pullAll, pushChanged, status, saveCustomerAttributes } from './sync.js';
5
+ import { pullAll, pushChanged, status, saveCustomerAttributes, pullConversations } from './sync.js';
6
6
  import { parseAkbFile, prepareArticlesForImport } from './akb.js';
7
7
  import { initializeEnvironment, ENV, EnvValidationError } from './env.js';
8
8
  import { parseCustomerConfigAsync, listCustomers, getCustomer, getDefaultCustomer, tryGetDefaultCustomer, getAllCustomers, validateCustomerConfig } from './customerAsync.js';
@@ -160,12 +160,14 @@ Usage:
160
160
  newo pull [--customer <idn>] # download projects -> ./newo_customers/<idn>/projects/
161
161
  newo push [--customer <idn>] # upload modified *.guidance/*.jinja back to NEWO
162
162
  newo status [--customer <idn>] # show modified files
163
+ newo conversations [--customer <idn>] [--all] # download user conversations -> ./newo_customers/<idn>/conversations.yaml
163
164
  newo list-customers # list available customers
164
165
  newo meta [--customer <idn>] # get project metadata (debug)
165
166
  newo import-akb <file> <persona_id> [--customer <idn>] # import AKB articles from file
166
167
 
167
168
  Flags:
168
169
  --customer <idn> # specify customer (if not set, uses default or interactive selection)
170
+ --all # include all available data (for conversations: all personas and acts)
169
171
  --verbose, -v # enable detailed logging
170
172
 
171
173
  Environment Variables:
@@ -343,6 +345,53 @@ File Structure:
343
345
  }
344
346
  return;
345
347
  }
348
+ if (cmd === 'conversations') {
349
+ // Handle customer selection for conversations command
350
+ if (args.customer) {
351
+ const customer = getCustomer(customerConfig, args.customer);
352
+ if (!customer) {
353
+ console.error(`Unknown customer: ${args.customer}`);
354
+ console.error(`Available customers: ${listCustomers(customerConfig).join(', ')}`);
355
+ process.exit(1);
356
+ }
357
+ selectedCustomer = customer;
358
+ }
359
+ else {
360
+ // Try to get default, fall back to all customers
361
+ selectedCustomer = tryGetDefaultCustomer(customerConfig);
362
+ if (!selectedCustomer) {
363
+ allCustomers = getAllCustomers(customerConfig);
364
+ if (verbose)
365
+ console.log(`💬 No default customer specified, pulling conversations from all ${allCustomers.length} customers`);
366
+ }
367
+ }
368
+ // Parse conversation-specific options - load all data by default
369
+ const conversationOptions = {
370
+ includeAll: true, // Always include all data for conversations
371
+ maxPersonas: undefined, // No limit on personas
372
+ maxActsPerPersona: undefined // No limit on acts per persona
373
+ };
374
+ if (selectedCustomer) {
375
+ // Single customer conversations
376
+ const accessToken = await getValidAccessToken(selectedCustomer);
377
+ const client = await makeClient(verbose, accessToken);
378
+ console.log(`💬 Pulling conversations for customer: ${selectedCustomer.idn} (all data)`);
379
+ await pullConversations(client, selectedCustomer, conversationOptions, verbose);
380
+ console.log(`✅ Conversations saved to newo_customers/${selectedCustomer.idn}/conversations.yaml`);
381
+ }
382
+ else if (allCustomers.length > 0) {
383
+ // Multi-customer conversations
384
+ console.log(`💬 Pulling conversations from ${allCustomers.length} customers (all data)...`);
385
+ for (const customer of allCustomers) {
386
+ console.log(`\n💬 Pulling conversations for customer: ${customer.idn}`);
387
+ const accessToken = await getValidAccessToken(customer);
388
+ const client = await makeClient(verbose, accessToken);
389
+ await pullConversations(client, customer, conversationOptions, verbose);
390
+ }
391
+ console.log(`\n✅ Conversations pull completed for all ${allCustomers.length} customers`);
392
+ }
393
+ return;
394
+ }
346
395
  // For all other commands, require a single selected customer
347
396
  if (args.customer) {
348
397
  const customer = getCustomer(customerConfig, args.customer);
package/dist/sync.d.ts CHANGED
@@ -1,8 +1,9 @@
1
1
  import type { AxiosInstance } from 'axios';
2
- import type { ProjectData, CustomerConfig } from './types.js';
2
+ import type { ProjectData, CustomerConfig, ConversationOptions } from './types.js';
3
3
  export declare function saveCustomerAttributes(client: AxiosInstance, customer: CustomerConfig, verbose?: boolean): Promise<void>;
4
4
  export declare function pullSingleProject(client: AxiosInstance, customer: CustomerConfig, projectId: string, projectIdn: string, verbose?: boolean): Promise<ProjectData>;
5
5
  export declare function pullAll(client: AxiosInstance, customer: CustomerConfig, projectId?: string | null, verbose?: boolean): Promise<void>;
6
6
  export declare function pushChanged(client: AxiosInstance, customer: CustomerConfig, verbose?: boolean): Promise<void>;
7
7
  export declare function status(customer: CustomerConfig, verbose?: boolean): Promise<void>;
8
+ export declare function pullConversations(client: AxiosInstance, customer: CustomerConfig, options?: ConversationOptions, verbose?: boolean): Promise<void>;
8
9
  //# sourceMappingURL=sync.d.ts.map
package/dist/sync.js CHANGED
@@ -1,4 +1,4 @@
1
- import { listProjects, listAgents, listFlowSkills, updateSkill, listFlowEvents, listFlowStates, getProjectMeta, getCustomerAttributes, updateCustomerAttribute } from './api.js';
1
+ import { listProjects, listAgents, listFlowSkills, updateSkill, listFlowEvents, listFlowStates, getProjectMeta, getCustomerAttributes, updateCustomerAttribute, listUserPersonas, getConversationActs, getChatHistory } from './api.js';
2
2
  import { ensureState, skillPath, skillScriptPath, writeFileSafe, readIfExists, mapPath, projectMetadataPath, agentMetadataPath, flowMetadataPath, skillMetadataPath, flowsYamlPath, customerAttributesPath, customerAttributesMapPath, customerAttributesBackupPath } from './fsutil.js';
3
3
  import fs from 'fs-extra';
4
4
  import { sha256, loadHashes, saveHashes } from './hash.js';
@@ -1018,4 +1018,203 @@ async function generateFlowsYaml(client, customer, agents, verbose = false) {
1018
1018
  await writeFileSafe(yamlPath, yamlContent);
1019
1019
  console.log(`✓ Generated flows.yaml`);
1020
1020
  }
1021
+ // Conversation sync functions
1022
+ export async function pullConversations(client, customer, options = {}, verbose = false) {
1023
+ if (verbose)
1024
+ console.log(`💬 Fetching conversations for customer ${customer.idn}...`);
1025
+ try {
1026
+ // Get all user personas with pagination
1027
+ const allPersonas = [];
1028
+ let page = 1;
1029
+ const perPage = 50;
1030
+ let hasMore = true;
1031
+ while (hasMore) {
1032
+ const response = await listUserPersonas(client, page, perPage);
1033
+ allPersonas.push(...response.items);
1034
+ if (verbose)
1035
+ console.log(`📋 Page ${page}: Found ${response.items.length} personas (${allPersonas.length}/${response.metadata.total} total)`);
1036
+ hasMore = response.items.length === perPage && allPersonas.length < response.metadata.total;
1037
+ page++;
1038
+ }
1039
+ if (options.maxPersonas && allPersonas.length > options.maxPersonas) {
1040
+ allPersonas.splice(options.maxPersonas);
1041
+ if (verbose)
1042
+ console.log(`⚠️ Limited to ${options.maxPersonas} personas as requested`);
1043
+ }
1044
+ if (verbose)
1045
+ console.log(`👥 Processing ${allPersonas.length} personas...`);
1046
+ // Process personas concurrently with limited concurrency
1047
+ const processedPersonas = [];
1048
+ await Promise.all(allPersonas.map(persona => concurrencyLimit(async () => {
1049
+ try {
1050
+ // Extract phone number from actors
1051
+ const phoneActor = persona.actors.find(actor => actor.integration_idn === 'newo_voice' &&
1052
+ actor.connector_idn === 'newo_voice_connector' &&
1053
+ actor.contact_information?.startsWith('+'));
1054
+ const phone = phoneActor?.contact_information || null;
1055
+ // Get acts for this persona
1056
+ const allActs = [];
1057
+ let actPage = 1;
1058
+ const actsPerPage = 100; // Higher limit for acts
1059
+ let hasMoreActs = true;
1060
+ while (hasMoreActs) {
1061
+ // First try the chat history API as alternative
1062
+ try {
1063
+ // Get user actor IDs from persona actors
1064
+ const userActors = persona.actors.filter(actor => actor.integration_idn === 'newo_voice' &&
1065
+ actor.connector_idn === 'newo_voice_connector');
1066
+ if (userActors.length > 0) {
1067
+ const chatHistoryParams = {
1068
+ user_actor_id: userActors[0].id,
1069
+ page: actPage,
1070
+ per: actsPerPage
1071
+ };
1072
+ const chatResponse = await getChatHistory(client, chatHistoryParams);
1073
+ if (chatResponse.items && chatResponse.items.length > 0) {
1074
+ // Convert chat history format to acts format - create minimal ConversationAct objects
1075
+ const convertedActs = chatResponse.items.map((item) => ({
1076
+ id: item.id || `chat_${Math.random()}`,
1077
+ command_act_id: null,
1078
+ external_event_id: item.external_event_id || 'chat_history',
1079
+ arguments: [],
1080
+ reference_idn: (item.is_agent === true) ? 'agent_message' : 'user_message',
1081
+ runtime_context_id: item.runtime_context_id || 'chat_history',
1082
+ source_text: item.payload?.text || item.message || item.content || item.text || '',
1083
+ original_text: item.payload?.text || item.message || item.content || item.text || '',
1084
+ datetime: item.datetime || item.created_at || item.timestamp || new Date().toISOString(),
1085
+ user_actor_id: userActors[0].id,
1086
+ agent_actor_id: null,
1087
+ user_persona_id: persona.id,
1088
+ user_persona_name: persona.name,
1089
+ agent_persona_id: item.agent_persona_id || 'unknown',
1090
+ external_id: item.external_id || null,
1091
+ integration_idn: 'newo_voice',
1092
+ connector_idn: 'newo_voice_connector',
1093
+ to_integration_idn: null,
1094
+ to_connector_idn: null,
1095
+ is_agent: Boolean(item.is_agent === true),
1096
+ project_idn: null,
1097
+ flow_idn: item.flow_idn || 'unknown',
1098
+ skill_idn: item.skill_idn || 'unknown',
1099
+ session_id: item.session_id || 'unknown',
1100
+ recordings: item.recordings || [],
1101
+ contact_information: item.contact_information || null
1102
+ }));
1103
+ allActs.push(...convertedActs);
1104
+ if (verbose && convertedActs.length > 0) {
1105
+ console.log(` 👤 ${persona.name}: Chat History - ${convertedActs.length} messages (${allActs.length} total)`);
1106
+ }
1107
+ hasMoreActs = chatResponse.items.length === actsPerPage &&
1108
+ chatResponse.metadata?.total !== undefined &&
1109
+ allActs.length < chatResponse.metadata.total;
1110
+ actPage++;
1111
+ continue; // Skip the original acts API call
1112
+ }
1113
+ }
1114
+ }
1115
+ catch (chatError) {
1116
+ if (verbose)
1117
+ console.log(` ⚠️ Chat history failed for ${persona.name}, trying acts API: ${chatError instanceof Error ? chatError.message : String(chatError)}`);
1118
+ }
1119
+ // Fallback to original acts API
1120
+ const actsParams = {
1121
+ user_persona_id: persona.id,
1122
+ page: actPage,
1123
+ per: actsPerPage,
1124
+ turn_type: 'messages',
1125
+ connectors: options.connectors?.join(',') || 'newo_voice/*',
1126
+ ...(options.fromDate && { from_date: options.fromDate }),
1127
+ ...(options.toDate && { to_date: options.toDate })
1128
+ };
1129
+ const actsResponse = await getConversationActs(client, actsParams);
1130
+ allActs.push(...actsResponse.items);
1131
+ if (verbose && actsResponse.items.length > 0) {
1132
+ console.log(` 👤 ${persona.name}: Page ${actPage} - ${actsResponse.items.length} acts (${allActs.length}/${actsResponse.metadata.total} total)`);
1133
+ }
1134
+ hasMoreActs = actsResponse.items.length === actsPerPage && allActs.length < actsResponse.metadata.total;
1135
+ actPage++;
1136
+ // Apply per-persona act limit if specified
1137
+ if (options.maxActsPerPersona && allActs.length >= options.maxActsPerPersona) {
1138
+ allActs.splice(options.maxActsPerPersona);
1139
+ hasMoreActs = false;
1140
+ if (verbose)
1141
+ console.log(` ⚠️ Limited to ${options.maxActsPerPersona} acts for ${persona.name}`);
1142
+ }
1143
+ }
1144
+ // Sort acts by datetime ascending (chronological order)
1145
+ allActs.sort((a, b) => new Date(a.datetime).getTime() - new Date(b.datetime).getTime());
1146
+ // Process acts into simplified format - exclude redundant fields
1147
+ const processedActs = allActs.map(act => {
1148
+ const processedAct = {
1149
+ datetime: act.datetime,
1150
+ type: act.reference_idn,
1151
+ message: act.source_text
1152
+ };
1153
+ // Only include non-redundant fields
1154
+ if (act.contact_information) {
1155
+ processedAct.contact_information = act.contact_information;
1156
+ }
1157
+ if (act.flow_idn && act.flow_idn !== 'unknown') {
1158
+ processedAct.flow_idn = act.flow_idn;
1159
+ }
1160
+ if (act.skill_idn && act.skill_idn !== 'unknown') {
1161
+ processedAct.skill_idn = act.skill_idn;
1162
+ }
1163
+ if (act.session_id && act.session_id !== 'unknown') {
1164
+ processedAct.session_id = act.session_id;
1165
+ }
1166
+ return processedAct;
1167
+ });
1168
+ processedPersonas.push({
1169
+ id: persona.id,
1170
+ name: persona.name,
1171
+ phone,
1172
+ act_count: persona.act_count,
1173
+ acts: processedActs
1174
+ });
1175
+ if (verbose)
1176
+ console.log(` ✓ Processed ${persona.name}: ${processedActs.length} acts`);
1177
+ }
1178
+ catch (error) {
1179
+ console.error(`❌ Failed to process persona ${persona.name}:`, error);
1180
+ // Continue with other personas
1181
+ }
1182
+ })));
1183
+ // Sort personas by most recent act time (descending) - use latest act from acts array
1184
+ processedPersonas.sort((a, b) => {
1185
+ const aLatestTime = a.acts.length > 0 ? a.acts[a.acts.length - 1].datetime : '1970-01-01T00:00:00.000Z';
1186
+ const bLatestTime = b.acts.length > 0 ? b.acts[b.acts.length - 1].datetime : '1970-01-01T00:00:00.000Z';
1187
+ return new Date(bLatestTime).getTime() - new Date(aLatestTime).getTime();
1188
+ });
1189
+ // Calculate totals
1190
+ const totalActs = processedPersonas.reduce((sum, persona) => sum + persona.acts.length, 0);
1191
+ // Create final conversations data
1192
+ const conversationsData = {
1193
+ personas: processedPersonas,
1194
+ total_personas: processedPersonas.length,
1195
+ total_acts: totalActs,
1196
+ generated_at: new Date().toISOString()
1197
+ };
1198
+ // Save to YAML file
1199
+ const conversationsPath = `newo_customers/${customer.idn}/conversations.yaml`;
1200
+ const yamlContent = yaml.dump(conversationsData, {
1201
+ indent: 2,
1202
+ quotingType: '"',
1203
+ forceQuotes: false,
1204
+ lineWidth: 120,
1205
+ noRefs: true,
1206
+ sortKeys: false,
1207
+ flowLevel: -1
1208
+ });
1209
+ await writeFileSafe(conversationsPath, yamlContent);
1210
+ if (verbose) {
1211
+ console.log(`✓ Saved conversations to ${conversationsPath}`);
1212
+ console.log(`📊 Summary: ${processedPersonas.length} personas, ${totalActs} total acts`);
1213
+ }
1214
+ }
1215
+ catch (error) {
1216
+ console.error(`❌ Failed to pull conversations for ${customer.idn}:`, error);
1217
+ throw error;
1218
+ }
1219
+ }
1021
1220
  //# sourceMappingURL=sync.js.map
package/dist/types.d.ts CHANGED
@@ -281,4 +281,122 @@ export interface SkillMetadata {
281
281
  created_at?: string;
282
282
  updated_at?: string;
283
283
  }
284
+ export interface Actor {
285
+ readonly id: string;
286
+ readonly conversation_is_active: boolean;
287
+ readonly act_count: number;
288
+ readonly external_id: string;
289
+ readonly integration_idn: string;
290
+ readonly connector_idn: string;
291
+ readonly contact_information: string;
292
+ }
293
+ export interface UserPersona {
294
+ readonly id: string;
295
+ readonly name: string;
296
+ readonly last_session_id: string;
297
+ readonly session_is_active: boolean;
298
+ readonly last_act_time: string;
299
+ readonly last_act_text: string;
300
+ readonly act_count: number;
301
+ readonly actors: readonly Actor[];
302
+ readonly not_found: boolean;
303
+ }
304
+ export interface ConversationAct {
305
+ readonly id: string;
306
+ readonly command_act_id: string | null;
307
+ readonly external_event_id: string;
308
+ readonly arguments: readonly any[];
309
+ readonly reference_idn: string;
310
+ readonly runtime_context_id: string;
311
+ readonly source_text: string;
312
+ readonly original_text: string;
313
+ readonly datetime: string;
314
+ readonly user_actor_id: string;
315
+ readonly agent_actor_id: string | null;
316
+ readonly user_persona_id: string;
317
+ readonly user_persona_name: string;
318
+ readonly agent_persona_id: string;
319
+ readonly external_id: string | null;
320
+ readonly integration_idn: string;
321
+ readonly connector_idn: string;
322
+ readonly to_integration_idn: string | null;
323
+ readonly to_connector_idn: string | null;
324
+ readonly is_agent: boolean;
325
+ readonly project_idn: string | null;
326
+ readonly flow_idn: string;
327
+ readonly skill_idn: string;
328
+ readonly session_id: string;
329
+ readonly recordings: readonly any[];
330
+ readonly contact_information: string | null;
331
+ }
332
+ export interface UserPersonaResponse {
333
+ readonly items: readonly UserPersona[];
334
+ readonly metadata: {
335
+ readonly page: number;
336
+ readonly per: number;
337
+ readonly total: number;
338
+ };
339
+ }
340
+ export interface ConversationActsResponse {
341
+ readonly items: readonly ConversationAct[];
342
+ readonly metadata: {
343
+ readonly page: number;
344
+ readonly per: number;
345
+ readonly total: number;
346
+ };
347
+ }
348
+ export interface ConversationActsParams {
349
+ readonly turn_type?: string;
350
+ readonly connectors?: string;
351
+ readonly user_persona_id: string;
352
+ readonly page?: number;
353
+ readonly per?: number;
354
+ readonly from_date?: string;
355
+ readonly to_date?: string;
356
+ }
357
+ export interface ChatHistoryParams {
358
+ readonly user_actor_id: string;
359
+ readonly agent_actor_id?: string;
360
+ readonly page?: number;
361
+ readonly per?: number;
362
+ }
363
+ export interface ChatHistoryResponse {
364
+ readonly items: readonly any[];
365
+ readonly metadata?: {
366
+ readonly page: number;
367
+ readonly per: number;
368
+ readonly total: number;
369
+ };
370
+ }
371
+ export interface ConversationOptions {
372
+ readonly includeAll?: boolean;
373
+ readonly connectors?: string[];
374
+ readonly fromDate?: string;
375
+ readonly toDate?: string;
376
+ readonly fields?: string[];
377
+ readonly maxPersonas?: number | undefined;
378
+ readonly maxActsPerPersona?: number | undefined;
379
+ }
380
+ export interface ProcessedAct {
381
+ readonly datetime: string;
382
+ readonly type: string;
383
+ readonly message: string;
384
+ readonly contact_information?: string | null;
385
+ readonly flow_idn?: string;
386
+ readonly skill_idn?: string;
387
+ readonly session_id?: string;
388
+ }
389
+ export interface ProcessedPersona {
390
+ readonly id: string;
391
+ readonly name: string;
392
+ readonly phone: string | null;
393
+ readonly act_count: number;
394
+ readonly acts: readonly ProcessedAct[];
395
+ }
396
+ export interface ConversationsData {
397
+ readonly personas: readonly ProcessedPersona[];
398
+ readonly total_personas: number;
399
+ readonly total_acts: number;
400
+ readonly generated_at: string;
401
+ }
284
402
  //# sourceMappingURL=types.d.ts.map
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "newo",
3
- "version": "1.8.0",
4
- "description": "NEWO CLI: comprehensive sync for AI Agent skills, customer attributes, and metadata between NEWO platform and local files. Multi-customer workspaces, complete change tracking, Git-first workflows.",
3
+ "version": "1.9.0",
4
+ "description": "NEWO CLI: comprehensive sync for AI Agent skills, customer attributes, conversations, and metadata between NEWO platform and local files. Multi-customer workspaces, conversation history extraction, complete change tracking, Git-first workflows.",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "newo": "dist/cli.js"
@@ -26,7 +26,10 @@
26
26
  "knowledge-base",
27
27
  "import",
28
28
  "multi-project",
29
- "workspace"
29
+ "workspace",
30
+ "conversations",
31
+ "chat-history",
32
+ "personas"
30
33
  ],
31
34
  "author": "sabbah13",
32
35
  "license": "MIT",
@@ -71,6 +74,8 @@
71
74
  "pull": "npm run build && node ./dist/cli.js pull",
72
75
  "push": "npm run build && node ./dist/cli.js push",
73
76
  "status": "npm run build && node ./dist/cli.js status",
77
+ "conversations": "npm run build && node ./dist/cli.js conversations",
78
+ "conversations:all": "npm run build && node ./dist/cli.js conversations --all",
74
79
  "clean": "rm -rf dist coverage",
75
80
  "typecheck": "tsc --noEmit",
76
81
  "lint": "tsc --noEmit --strict",
package/src/api.ts CHANGED
@@ -10,7 +10,13 @@ import type {
10
10
  AkbImportArticle,
11
11
  CustomerProfile,
12
12
  CustomerAttribute,
13
- CustomerAttributesResponse
13
+ CustomerAttributesResponse,
14
+ UserPersonaResponse,
15
+ UserPersona,
16
+ ConversationActsResponse,
17
+ ConversationActsParams,
18
+ ChatHistoryParams,
19
+ ChatHistoryResponse
14
20
  } from './types.js';
15
21
 
16
22
  // Per-request retry tracking to avoid shared state issues
@@ -152,4 +158,60 @@ export async function updateCustomerAttribute(client: AxiosInstance, attribute:
152
158
  possible_values: attribute.possible_values,
153
159
  value_type: attribute.value_type
154
160
  });
161
+ }
162
+
163
+ // Conversation API Functions
164
+
165
+ export async function listUserPersonas(client: AxiosInstance, page: number = 1, per: number = 50): Promise<UserPersonaResponse> {
166
+ const response = await client.get<UserPersonaResponse>('/api/v1/bff/conversations/user-personas', {
167
+ params: { page, per }
168
+ });
169
+ return response.data;
170
+ }
171
+
172
+ export async function getUserPersona(client: AxiosInstance, personaId: string): Promise<UserPersona> {
173
+ const response = await client.get<UserPersona>(`/api/v1/bff/conversations/user-personas/${personaId}`);
174
+ return response.data;
175
+ }
176
+
177
+ export async function getAccount(client: AxiosInstance): Promise<{ id: string; [key: string]: any }> {
178
+ const response = await client.get<{ id: string; [key: string]: any }>('/api/v1/account');
179
+ return response.data;
180
+ }
181
+
182
+ export async function getConversationActs(client: AxiosInstance, params: ConversationActsParams): Promise<ConversationActsResponse> {
183
+ const queryParams: Record<string, any> = {
184
+ user_persona_id: params.user_persona_id,
185
+ page: params.page || 1,
186
+ per: params.per || 50
187
+ };
188
+
189
+ // Only add optional parameters if explicitly provided
190
+ if (params.turn_type) queryParams.turn_type = params.turn_type;
191
+ if (params.connectors) queryParams.connectors = params.connectors;
192
+ if (params.from_date) queryParams.from_date = params.from_date;
193
+ if (params.to_date) queryParams.to_date = params.to_date;
194
+
195
+ const response = await client.get<ConversationActsResponse>('/api/v1/bff/conversations/acts', {
196
+ params: queryParams
197
+ });
198
+ return response.data;
199
+ }
200
+
201
+ export async function getChatHistory(client: AxiosInstance, params: ChatHistoryParams): Promise<ChatHistoryResponse> {
202
+ const queryParams: Record<string, any> = {
203
+ user_actor_id: params.user_actor_id,
204
+ page: params.page || 1,
205
+ per: params.per || 50
206
+ };
207
+
208
+ // Only add agent_actor_id if provided
209
+ if (params.agent_actor_id) {
210
+ queryParams.agent_actor_id = params.agent_actor_id;
211
+ }
212
+
213
+ const response = await client.get<ChatHistoryResponse>('/api/v1/chat/history', {
214
+ params: queryParams
215
+ });
216
+ return response.data;
155
217
  }
package/src/cli.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  import minimist from 'minimist';
3
3
  import dotenv from 'dotenv';
4
4
  import { makeClient, getProjectMeta, importAkbArticle } from './api.js';
5
- import { pullAll, pushChanged, status, saveCustomerAttributes } from './sync.js';
5
+ import { pullAll, pushChanged, status, saveCustomerAttributes, pullConversations } from './sync.js';
6
6
  import { parseAkbFile, prepareArticlesForImport } from './akb.js';
7
7
  import { initializeEnvironment, ENV, EnvValidationError } from './env.js';
8
8
  import { parseCustomerConfigAsync, listCustomers, getCustomer, getDefaultCustomer, tryGetDefaultCustomer, getAllCustomers, validateCustomerConfig } from './customerAsync.js';
@@ -168,12 +168,14 @@ Usage:
168
168
  newo pull [--customer <idn>] # download projects -> ./newo_customers/<idn>/projects/
169
169
  newo push [--customer <idn>] # upload modified *.guidance/*.jinja back to NEWO
170
170
  newo status [--customer <idn>] # show modified files
171
+ newo conversations [--customer <idn>] [--all] # download user conversations -> ./newo_customers/<idn>/conversations.yaml
171
172
  newo list-customers # list available customers
172
173
  newo meta [--customer <idn>] # get project metadata (debug)
173
174
  newo import-akb <file> <persona_id> [--customer <idn>] # import AKB articles from file
174
175
 
175
176
  Flags:
176
177
  --customer <idn> # specify customer (if not set, uses default or interactive selection)
178
+ --all # include all available data (for conversations: all personas and acts)
177
179
  --verbose, -v # enable detailed logging
178
180
 
179
181
  Environment Variables:
@@ -352,6 +354,53 @@ File Structure:
352
354
  return;
353
355
  }
354
356
 
357
+ if (cmd === 'conversations') {
358
+ // Handle customer selection for conversations command
359
+ if (args.customer) {
360
+ const customer = getCustomer(customerConfig, args.customer as string);
361
+ if (!customer) {
362
+ console.error(`Unknown customer: ${args.customer}`);
363
+ console.error(`Available customers: ${listCustomers(customerConfig).join(', ')}`);
364
+ process.exit(1);
365
+ }
366
+ selectedCustomer = customer;
367
+ } else {
368
+ // Try to get default, fall back to all customers
369
+ selectedCustomer = tryGetDefaultCustomer(customerConfig);
370
+ if (!selectedCustomer) {
371
+ allCustomers = getAllCustomers(customerConfig);
372
+ if (verbose) console.log(`💬 No default customer specified, pulling conversations from all ${allCustomers.length} customers`);
373
+ }
374
+ }
375
+
376
+ // Parse conversation-specific options - load all data by default
377
+ const conversationOptions = {
378
+ includeAll: true, // Always include all data for conversations
379
+ maxPersonas: undefined, // No limit on personas
380
+ maxActsPerPersona: undefined // No limit on acts per persona
381
+ };
382
+
383
+ if (selectedCustomer) {
384
+ // Single customer conversations
385
+ const accessToken = await getValidAccessToken(selectedCustomer);
386
+ const client = await makeClient(verbose, accessToken);
387
+ console.log(`💬 Pulling conversations for customer: ${selectedCustomer.idn} (all data)`);
388
+ await pullConversations(client, selectedCustomer, conversationOptions, verbose);
389
+ console.log(`✅ Conversations saved to newo_customers/${selectedCustomer.idn}/conversations.yaml`);
390
+ } else if (allCustomers.length > 0) {
391
+ // Multi-customer conversations
392
+ console.log(`💬 Pulling conversations from ${allCustomers.length} customers (all data)...`);
393
+ for (const customer of allCustomers) {
394
+ console.log(`\n💬 Pulling conversations for customer: ${customer.idn}`);
395
+ const accessToken = await getValidAccessToken(customer);
396
+ const client = await makeClient(verbose, accessToken);
397
+ await pullConversations(client, customer, conversationOptions, verbose);
398
+ }
399
+ console.log(`\n✅ Conversations pull completed for all ${allCustomers.length} customers`);
400
+ }
401
+ return;
402
+ }
403
+
355
404
  // For all other commands, require a single selected customer
356
405
  if (args.customer) {
357
406
  const customer = getCustomer(customerConfig, args.customer as string);
package/src/sync.ts CHANGED
@@ -7,7 +7,10 @@ import {
7
7
  listFlowStates,
8
8
  getProjectMeta,
9
9
  getCustomerAttributes,
10
- updateCustomerAttribute
10
+ updateCustomerAttribute,
11
+ listUserPersonas,
12
+ getConversationActs,
13
+ getChatHistory
11
14
  } from './api.js';
12
15
  import {
13
16
  ensureState,
@@ -49,7 +52,13 @@ import type {
49
52
  SkillMetadata,
50
53
  FlowEvent,
51
54
  FlowState,
52
- CustomerAttribute
55
+ CustomerAttribute,
56
+ UserPersona,
57
+ ConversationAct,
58
+ ConversationOptions,
59
+ ConversationsData,
60
+ ProcessedPersona,
61
+ ProcessedAct
53
62
  } from './types.js';
54
63
 
55
64
  // Concurrency limits for API operations
@@ -1171,4 +1180,237 @@ async function generateFlowsYaml(
1171
1180
  const yamlPath = flowsYamlPath(customer.idn);
1172
1181
  await writeFileSafe(yamlPath, yamlContent);
1173
1182
  console.log(`✓ Generated flows.yaml`);
1183
+ }
1184
+
1185
+ // Conversation sync functions
1186
+
1187
+ export async function pullConversations(
1188
+ client: AxiosInstance,
1189
+ customer: CustomerConfig,
1190
+ options: ConversationOptions = {},
1191
+ verbose: boolean = false
1192
+ ): Promise<void> {
1193
+ if (verbose) console.log(`💬 Fetching conversations for customer ${customer.idn}...`);
1194
+
1195
+ try {
1196
+ // Get all user personas with pagination
1197
+ const allPersonas: UserPersona[] = [];
1198
+ let page = 1;
1199
+ const perPage = 50;
1200
+ let hasMore = true;
1201
+
1202
+ while (hasMore) {
1203
+ const response = await listUserPersonas(client, page, perPage);
1204
+ allPersonas.push(...response.items);
1205
+
1206
+ if (verbose) console.log(`📋 Page ${page}: Found ${response.items.length} personas (${allPersonas.length}/${response.metadata.total} total)`);
1207
+
1208
+ hasMore = response.items.length === perPage && allPersonas.length < response.metadata.total;
1209
+ page++;
1210
+ }
1211
+
1212
+ if (options.maxPersonas && allPersonas.length > options.maxPersonas) {
1213
+ allPersonas.splice(options.maxPersonas);
1214
+ if (verbose) console.log(`⚠️ Limited to ${options.maxPersonas} personas as requested`);
1215
+ }
1216
+
1217
+ if (verbose) console.log(`👥 Processing ${allPersonas.length} personas...`);
1218
+
1219
+ // Process personas concurrently with limited concurrency
1220
+ const processedPersonas: ProcessedPersona[] = [];
1221
+
1222
+ await Promise.all(allPersonas.map(persona => concurrencyLimit(async () => {
1223
+ try {
1224
+ // Extract phone number from actors
1225
+ const phoneActor = persona.actors.find(actor =>
1226
+ actor.integration_idn === 'newo_voice' &&
1227
+ actor.connector_idn === 'newo_voice_connector' &&
1228
+ actor.contact_information?.startsWith('+')
1229
+ );
1230
+ const phone = phoneActor?.contact_information || null;
1231
+
1232
+ // Get acts for this persona
1233
+ const allActs: ConversationAct[] = [];
1234
+ let actPage = 1;
1235
+ const actsPerPage = 100; // Higher limit for acts
1236
+ let hasMoreActs = true;
1237
+
1238
+ while (hasMoreActs) {
1239
+ // First try the chat history API as alternative
1240
+ try {
1241
+ // Get user actor IDs from persona actors
1242
+ const userActors = persona.actors.filter(actor =>
1243
+ actor.integration_idn === 'newo_voice' &&
1244
+ actor.connector_idn === 'newo_voice_connector'
1245
+ );
1246
+
1247
+ if (userActors.length > 0) {
1248
+ const chatHistoryParams = {
1249
+ user_actor_id: userActors[0]!.id,
1250
+ page: actPage,
1251
+ per: actsPerPage
1252
+ };
1253
+
1254
+ const chatResponse = await getChatHistory(client, chatHistoryParams);
1255
+ if (chatResponse.items && chatResponse.items.length > 0) {
1256
+ // Convert chat history format to acts format - create minimal ConversationAct objects
1257
+ const convertedActs: ConversationAct[] = chatResponse.items.map((item: any) => ({
1258
+ id: item.id || `chat_${Math.random()}`,
1259
+ command_act_id: null,
1260
+ external_event_id: item.external_event_id || 'chat_history',
1261
+ arguments: [],
1262
+ reference_idn: (item.is_agent === true) ? 'agent_message' : 'user_message',
1263
+ runtime_context_id: item.runtime_context_id || 'chat_history',
1264
+ source_text: item.payload?.text || item.message || item.content || item.text || '',
1265
+ original_text: item.payload?.text || item.message || item.content || item.text || '',
1266
+ datetime: item.datetime || item.created_at || item.timestamp || new Date().toISOString(),
1267
+ user_actor_id: userActors[0]!.id,
1268
+ agent_actor_id: null,
1269
+ user_persona_id: persona.id,
1270
+ user_persona_name: persona.name,
1271
+ agent_persona_id: item.agent_persona_id || 'unknown',
1272
+ external_id: item.external_id || null,
1273
+ integration_idn: 'newo_voice',
1274
+ connector_idn: 'newo_voice_connector',
1275
+ to_integration_idn: null,
1276
+ to_connector_idn: null,
1277
+ is_agent: Boolean(item.is_agent === true),
1278
+ project_idn: null,
1279
+ flow_idn: item.flow_idn || 'unknown',
1280
+ skill_idn: item.skill_idn || 'unknown',
1281
+ session_id: item.session_id || 'unknown',
1282
+ recordings: item.recordings || [],
1283
+ contact_information: item.contact_information || null
1284
+ }));
1285
+
1286
+ allActs.push(...convertedActs);
1287
+
1288
+ if (verbose && convertedActs.length > 0) {
1289
+ console.log(` 👤 ${persona.name}: Chat History - ${convertedActs.length} messages (${allActs.length} total)`);
1290
+ }
1291
+
1292
+ hasMoreActs = chatResponse.items.length === actsPerPage &&
1293
+ chatResponse.metadata?.total !== undefined &&
1294
+ allActs.length < chatResponse.metadata.total;
1295
+ actPage++;
1296
+ continue; // Skip the original acts API call
1297
+ }
1298
+ }
1299
+ } catch (chatError) {
1300
+ if (verbose) console.log(` ⚠️ Chat history failed for ${persona.name}, trying acts API: ${chatError instanceof Error ? chatError.message : String(chatError)}`);
1301
+ }
1302
+
1303
+ // Fallback to original acts API
1304
+ const actsParams = {
1305
+ user_persona_id: persona.id,
1306
+ page: actPage,
1307
+ per: actsPerPage,
1308
+ turn_type: 'messages',
1309
+ connectors: options.connectors?.join(',') || 'newo_voice/*',
1310
+ ...(options.fromDate && { from_date: options.fromDate }),
1311
+ ...(options.toDate && { to_date: options.toDate })
1312
+ };
1313
+
1314
+ const actsResponse = await getConversationActs(client, actsParams);
1315
+ allActs.push(...actsResponse.items);
1316
+
1317
+ if (verbose && actsResponse.items.length > 0) {
1318
+ console.log(` 👤 ${persona.name}: Page ${actPage} - ${actsResponse.items.length} acts (${allActs.length}/${actsResponse.metadata.total} total)`);
1319
+ }
1320
+
1321
+ hasMoreActs = actsResponse.items.length === actsPerPage && allActs.length < actsResponse.metadata.total;
1322
+ actPage++;
1323
+
1324
+ // Apply per-persona act limit if specified
1325
+ if (options.maxActsPerPersona && allActs.length >= options.maxActsPerPersona) {
1326
+ allActs.splice(options.maxActsPerPersona);
1327
+ hasMoreActs = false;
1328
+ if (verbose) console.log(` ⚠️ Limited to ${options.maxActsPerPersona} acts for ${persona.name}`);
1329
+ }
1330
+ }
1331
+
1332
+ // Sort acts by datetime ascending (chronological order)
1333
+ allActs.sort((a, b) => new Date(a.datetime).getTime() - new Date(b.datetime).getTime());
1334
+
1335
+ // Process acts into simplified format - exclude redundant fields
1336
+ const processedActs: ProcessedAct[] = allActs.map(act => {
1337
+ const processedAct: ProcessedAct = {
1338
+ datetime: act.datetime,
1339
+ type: act.reference_idn,
1340
+ message: act.source_text
1341
+ };
1342
+
1343
+ // Only include non-redundant fields
1344
+ if (act.contact_information) {
1345
+ (processedAct as any).contact_information = act.contact_information;
1346
+ }
1347
+ if (act.flow_idn && act.flow_idn !== 'unknown') {
1348
+ (processedAct as any).flow_idn = act.flow_idn;
1349
+ }
1350
+ if (act.skill_idn && act.skill_idn !== 'unknown') {
1351
+ (processedAct as any).skill_idn = act.skill_idn;
1352
+ }
1353
+ if (act.session_id && act.session_id !== 'unknown') {
1354
+ (processedAct as any).session_id = act.session_id;
1355
+ }
1356
+
1357
+ return processedAct;
1358
+ });
1359
+
1360
+ processedPersonas.push({
1361
+ id: persona.id,
1362
+ name: persona.name,
1363
+ phone,
1364
+ act_count: persona.act_count,
1365
+ acts: processedActs
1366
+ });
1367
+
1368
+ if (verbose) console.log(` ✓ Processed ${persona.name}: ${processedActs.length} acts`);
1369
+ } catch (error) {
1370
+ console.error(`❌ Failed to process persona ${persona.name}:`, error);
1371
+ // Continue with other personas
1372
+ }
1373
+ })));
1374
+
1375
+ // Sort personas by most recent act time (descending) - use latest act from acts array
1376
+ processedPersonas.sort((a, b) => {
1377
+ const aLatestTime = a.acts.length > 0 ? a.acts[a.acts.length - 1]!.datetime : '1970-01-01T00:00:00.000Z';
1378
+ const bLatestTime = b.acts.length > 0 ? b.acts[b.acts.length - 1]!.datetime : '1970-01-01T00:00:00.000Z';
1379
+ return new Date(bLatestTime).getTime() - new Date(aLatestTime).getTime();
1380
+ });
1381
+
1382
+ // Calculate totals
1383
+ const totalActs = processedPersonas.reduce((sum, persona) => sum + persona.acts.length, 0);
1384
+
1385
+ // Create final conversations data
1386
+ const conversationsData: ConversationsData = {
1387
+ personas: processedPersonas,
1388
+ total_personas: processedPersonas.length,
1389
+ total_acts: totalActs,
1390
+ generated_at: new Date().toISOString()
1391
+ };
1392
+
1393
+ // Save to YAML file
1394
+ const conversationsPath = `newo_customers/${customer.idn}/conversations.yaml`;
1395
+ const yamlContent = yaml.dump(conversationsData, {
1396
+ indent: 2,
1397
+ quotingType: '"',
1398
+ forceQuotes: false,
1399
+ lineWidth: 120,
1400
+ noRefs: true,
1401
+ sortKeys: false,
1402
+ flowLevel: -1
1403
+ });
1404
+
1405
+ await writeFileSafe(conversationsPath, yamlContent);
1406
+
1407
+ if (verbose) {
1408
+ console.log(`✓ Saved conversations to ${conversationsPath}`);
1409
+ console.log(`📊 Summary: ${processedPersonas.length} personas, ${totalActs} total acts`);
1410
+ }
1411
+
1412
+ } catch (error) {
1413
+ console.error(`❌ Failed to pull conversations for ${customer.idn}:`, error);
1414
+ throw error;
1415
+ }
1174
1416
  }
package/src/types.ts CHANGED
@@ -336,4 +336,136 @@ export interface SkillMetadata {
336
336
  path?: string;
337
337
  created_at?: string;
338
338
  updated_at?: string;
339
+ }
340
+
341
+ // Conversation Types
342
+ export interface Actor {
343
+ readonly id: string;
344
+ readonly conversation_is_active: boolean;
345
+ readonly act_count: number;
346
+ readonly external_id: string;
347
+ readonly integration_idn: string;
348
+ readonly connector_idn: string;
349
+ readonly contact_information: string;
350
+ }
351
+
352
+ export interface UserPersona {
353
+ readonly id: string;
354
+ readonly name: string;
355
+ readonly last_session_id: string;
356
+ readonly session_is_active: boolean;
357
+ readonly last_act_time: string;
358
+ readonly last_act_text: string;
359
+ readonly act_count: number;
360
+ readonly actors: readonly Actor[];
361
+ readonly not_found: boolean;
362
+ }
363
+
364
+ export interface ConversationAct {
365
+ readonly id: string;
366
+ readonly command_act_id: string | null;
367
+ readonly external_event_id: string;
368
+ readonly arguments: readonly any[];
369
+ readonly reference_idn: string;
370
+ readonly runtime_context_id: string;
371
+ readonly source_text: string;
372
+ readonly original_text: string;
373
+ readonly datetime: string;
374
+ readonly user_actor_id: string;
375
+ readonly agent_actor_id: string | null;
376
+ readonly user_persona_id: string;
377
+ readonly user_persona_name: string;
378
+ readonly agent_persona_id: string;
379
+ readonly external_id: string | null;
380
+ readonly integration_idn: string;
381
+ readonly connector_idn: string;
382
+ readonly to_integration_idn: string | null;
383
+ readonly to_connector_idn: string | null;
384
+ readonly is_agent: boolean;
385
+ readonly project_idn: string | null;
386
+ readonly flow_idn: string;
387
+ readonly skill_idn: string;
388
+ readonly session_id: string;
389
+ readonly recordings: readonly any[];
390
+ readonly contact_information: string | null;
391
+ }
392
+
393
+ export interface UserPersonaResponse {
394
+ readonly items: readonly UserPersona[];
395
+ readonly metadata: {
396
+ readonly page: number;
397
+ readonly per: number;
398
+ readonly total: number;
399
+ };
400
+ }
401
+
402
+ export interface ConversationActsResponse {
403
+ readonly items: readonly ConversationAct[];
404
+ readonly metadata: {
405
+ readonly page: number;
406
+ readonly per: number;
407
+ readonly total: number;
408
+ };
409
+ }
410
+
411
+ export interface ConversationActsParams {
412
+ readonly turn_type?: string;
413
+ readonly connectors?: string;
414
+ readonly user_persona_id: string;
415
+ readonly page?: number;
416
+ readonly per?: number;
417
+ readonly from_date?: string;
418
+ readonly to_date?: string;
419
+ }
420
+
421
+ export interface ChatHistoryParams {
422
+ readonly user_actor_id: string;
423
+ readonly agent_actor_id?: string;
424
+ readonly page?: number;
425
+ readonly per?: number;
426
+ }
427
+
428
+ export interface ChatHistoryResponse {
429
+ readonly items: readonly any[]; // We'll define this after seeing the response structure
430
+ readonly metadata?: {
431
+ readonly page: number;
432
+ readonly per: number;
433
+ readonly total: number;
434
+ };
435
+ }
436
+
437
+ export interface ConversationOptions {
438
+ readonly includeAll?: boolean;
439
+ readonly connectors?: string[];
440
+ readonly fromDate?: string;
441
+ readonly toDate?: string;
442
+ readonly fields?: string[];
443
+ readonly maxPersonas?: number | undefined;
444
+ readonly maxActsPerPersona?: number | undefined;
445
+ }
446
+
447
+ // Processed conversation data for YAML output
448
+ export interface ProcessedAct {
449
+ readonly datetime: string;
450
+ readonly type: string;
451
+ readonly message: string;
452
+ readonly contact_information?: string | null;
453
+ readonly flow_idn?: string;
454
+ readonly skill_idn?: string;
455
+ readonly session_id?: string;
456
+ }
457
+
458
+ export interface ProcessedPersona {
459
+ readonly id: string;
460
+ readonly name: string;
461
+ readonly phone: string | null;
462
+ readonly act_count: number;
463
+ readonly acts: readonly ProcessedAct[];
464
+ }
465
+
466
+ export interface ConversationsData {
467
+ readonly personas: readonly ProcessedPersona[];
468
+ readonly total_personas: number;
469
+ readonly total_acts: number;
470
+ readonly generated_at: string;
339
471
  }