newo 1.8.0 → 1.9.1
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 +51 -0
- package/dist/api.d.ts +8 -1
- package/dist/api.js +30 -0
- package/dist/cli.js +50 -1
- package/dist/sync.d.ts +2 -1
- package/dist/sync.js +178 -1
- package/dist/types.d.ts +101 -0
- package/package.json +8 -3
- package/src/api.ts +43 -1
- package/src/cli.ts +50 -1
- package/src/sync.ts +217 -2
- package/src/types.ts +114 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,57 @@ 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.1] - 2025-09-16
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- **Clean Chat History Implementation**: Remove conversations acts API fallback entirely
|
|
12
|
+
- Eliminates all 403 "Invalid token or account_id field missing" errors
|
|
13
|
+
- Uses only `/api/v1/chat/history` endpoint which works with current API keys
|
|
14
|
+
- Removed unused `getConversationActs()` function and related types
|
|
15
|
+
- Clean implementation without permission-dependent fallbacks
|
|
16
|
+
|
|
17
|
+
### Removed
|
|
18
|
+
- **Obsolete Code Cleanup**: Remove unused conversation acts API components
|
|
19
|
+
- `getConversationActs()` function (unused after chat history integration)
|
|
20
|
+
- `ConversationActsParams` and `ConversationActsResponse` interfaces
|
|
21
|
+
- Fallback logic that caused 403 errors for personas without proper permissions
|
|
22
|
+
|
|
23
|
+
## [1.9.0] - 2025-09-16
|
|
24
|
+
|
|
25
|
+
### Added
|
|
26
|
+
- **User Conversations Pull Functionality**: Complete conversation history extraction
|
|
27
|
+
- New `newo conversations` command to download user conversations and personas
|
|
28
|
+
- Multi-customer conversation support with `--customer <idn>` flag
|
|
29
|
+
- Chat History API integration (`/api/v1/chat/history`) with fallback to conversations acts API
|
|
30
|
+
- Automatic phone number extraction from persona actors
|
|
31
|
+
- Comprehensive pagination handling for large conversation datasets
|
|
32
|
+
- Clean YAML output format in `newo_customers/{customerIdn}/conversations.yaml`
|
|
33
|
+
|
|
34
|
+
### Enhanced
|
|
35
|
+
- **Conversation Data Processing**: Optimized structure and chronological ordering
|
|
36
|
+
- Acts sorted by datetime ascending (chronological conversation flow)
|
|
37
|
+
- Personas sorted by most recent activity (descending)
|
|
38
|
+
- Redundant fields removed (`is_agent`, `session_id: unknown`, etc.)
|
|
39
|
+
- Clean persona structure: `id` → `name` → `phone` → `act_count` → `acts`
|
|
40
|
+
- Proper datetime extraction from chat history API responses
|
|
41
|
+
|
|
42
|
+
### Technical
|
|
43
|
+
- **New API Functions**: Type-safe conversation API integration
|
|
44
|
+
- `listUserPersonas()` - Get all user personas with pagination
|
|
45
|
+
- `getChatHistory()` - Get conversation history for user actors
|
|
46
|
+
- `getConversationActs()` - Fallback for accounts with proper permissions
|
|
47
|
+
- `pullConversations()` - Complete conversation sync orchestration
|
|
48
|
+
- **NPM Scripts**: Added convenient conversation commands
|
|
49
|
+
- `npm run conversations` - Build and pull conversations
|
|
50
|
+
- `npm run conversations:all` - Legacy alias for compatibility
|
|
51
|
+
|
|
52
|
+
### Performance
|
|
53
|
+
- **Concurrent Processing**: Efficient conversation data extraction
|
|
54
|
+
- Parallel API calls with concurrency limiting (p-limit)
|
|
55
|
+
- Graceful error handling with persona-level fault tolerance
|
|
56
|
+
- No artificial limits on personas or acts (loads all available data)
|
|
57
|
+
- Multi-customer support with authentication reuse
|
|
58
|
+
|
|
8
59
|
## [1.8.0] - 2025-09-15
|
|
9
60
|
|
|
10
61
|
### 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, 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,11 @@ 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 getChatHistory(client: AxiosInstance, params: ChatHistoryParams): Promise<ChatHistoryResponse>;
|
|
16
23
|
//# sourceMappingURL=api.d.ts.map
|
package/dist/api.js
CHANGED
|
@@ -121,4 +121,34 @@ 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 getChatHistory(client, params) {
|
|
140
|
+
const queryParams = {
|
|
141
|
+
user_actor_id: params.user_actor_id,
|
|
142
|
+
page: params.page || 1,
|
|
143
|
+
per: params.per || 50
|
|
144
|
+
};
|
|
145
|
+
// Only add agent_actor_id if provided
|
|
146
|
+
if (params.agent_actor_id) {
|
|
147
|
+
queryParams.agent_actor_id = params.agent_actor_id;
|
|
148
|
+
}
|
|
149
|
+
const response = await client.get('/api/v1/chat/history', {
|
|
150
|
+
params: queryParams
|
|
151
|
+
});
|
|
152
|
+
return response.data;
|
|
153
|
+
}
|
|
124
154
|
//# 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, 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,181 @@ 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}: ${chatError instanceof Error ? chatError.message : String(chatError)}`);
|
|
1118
|
+
// No fallback - only use chat history API
|
|
1119
|
+
hasMoreActs = false;
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
// Sort acts by datetime ascending (chronological order)
|
|
1123
|
+
allActs.sort((a, b) => new Date(a.datetime).getTime() - new Date(b.datetime).getTime());
|
|
1124
|
+
// Process acts into simplified format - exclude redundant fields
|
|
1125
|
+
const processedActs = allActs.map(act => {
|
|
1126
|
+
const processedAct = {
|
|
1127
|
+
datetime: act.datetime,
|
|
1128
|
+
type: act.reference_idn,
|
|
1129
|
+
message: act.source_text
|
|
1130
|
+
};
|
|
1131
|
+
// Only include non-redundant fields
|
|
1132
|
+
if (act.contact_information) {
|
|
1133
|
+
processedAct.contact_information = act.contact_information;
|
|
1134
|
+
}
|
|
1135
|
+
if (act.flow_idn && act.flow_idn !== 'unknown') {
|
|
1136
|
+
processedAct.flow_idn = act.flow_idn;
|
|
1137
|
+
}
|
|
1138
|
+
if (act.skill_idn && act.skill_idn !== 'unknown') {
|
|
1139
|
+
processedAct.skill_idn = act.skill_idn;
|
|
1140
|
+
}
|
|
1141
|
+
if (act.session_id && act.session_id !== 'unknown') {
|
|
1142
|
+
processedAct.session_id = act.session_id;
|
|
1143
|
+
}
|
|
1144
|
+
return processedAct;
|
|
1145
|
+
});
|
|
1146
|
+
processedPersonas.push({
|
|
1147
|
+
id: persona.id,
|
|
1148
|
+
name: persona.name,
|
|
1149
|
+
phone,
|
|
1150
|
+
act_count: persona.act_count,
|
|
1151
|
+
acts: processedActs
|
|
1152
|
+
});
|
|
1153
|
+
if (verbose)
|
|
1154
|
+
console.log(` ✓ Processed ${persona.name}: ${processedActs.length} acts`);
|
|
1155
|
+
}
|
|
1156
|
+
catch (error) {
|
|
1157
|
+
console.error(`❌ Failed to process persona ${persona.name}:`, error);
|
|
1158
|
+
// Continue with other personas
|
|
1159
|
+
}
|
|
1160
|
+
})));
|
|
1161
|
+
// Sort personas by most recent act time (descending) - use latest act from acts array
|
|
1162
|
+
processedPersonas.sort((a, b) => {
|
|
1163
|
+
const aLatestTime = a.acts.length > 0 ? a.acts[a.acts.length - 1].datetime : '1970-01-01T00:00:00.000Z';
|
|
1164
|
+
const bLatestTime = b.acts.length > 0 ? b.acts[b.acts.length - 1].datetime : '1970-01-01T00:00:00.000Z';
|
|
1165
|
+
return new Date(bLatestTime).getTime() - new Date(aLatestTime).getTime();
|
|
1166
|
+
});
|
|
1167
|
+
// Calculate totals
|
|
1168
|
+
const totalActs = processedPersonas.reduce((sum, persona) => sum + persona.acts.length, 0);
|
|
1169
|
+
// Create final conversations data
|
|
1170
|
+
const conversationsData = {
|
|
1171
|
+
personas: processedPersonas,
|
|
1172
|
+
total_personas: processedPersonas.length,
|
|
1173
|
+
total_acts: totalActs,
|
|
1174
|
+
generated_at: new Date().toISOString()
|
|
1175
|
+
};
|
|
1176
|
+
// Save to YAML file
|
|
1177
|
+
const conversationsPath = `newo_customers/${customer.idn}/conversations.yaml`;
|
|
1178
|
+
const yamlContent = yaml.dump(conversationsData, {
|
|
1179
|
+
indent: 2,
|
|
1180
|
+
quotingType: '"',
|
|
1181
|
+
forceQuotes: false,
|
|
1182
|
+
lineWidth: 120,
|
|
1183
|
+
noRefs: true,
|
|
1184
|
+
sortKeys: false,
|
|
1185
|
+
flowLevel: -1
|
|
1186
|
+
});
|
|
1187
|
+
await writeFileSafe(conversationsPath, yamlContent);
|
|
1188
|
+
if (verbose) {
|
|
1189
|
+
console.log(`✓ Saved conversations to ${conversationsPath}`);
|
|
1190
|
+
console.log(`📊 Summary: ${processedPersonas.length} personas, ${totalActs} total acts`);
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
catch (error) {
|
|
1194
|
+
console.error(`❌ Failed to pull conversations for ${customer.idn}:`, error);
|
|
1195
|
+
throw error;
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1021
1198
|
//# sourceMappingURL=sync.js.map
|
package/dist/types.d.ts
CHANGED
|
@@ -281,4 +281,105 @@ 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 ChatHistoryParams {
|
|
341
|
+
readonly user_actor_id: string;
|
|
342
|
+
readonly agent_actor_id?: string;
|
|
343
|
+
readonly page?: number;
|
|
344
|
+
readonly per?: number;
|
|
345
|
+
}
|
|
346
|
+
export interface ChatHistoryResponse {
|
|
347
|
+
readonly items: readonly any[];
|
|
348
|
+
readonly metadata?: {
|
|
349
|
+
readonly page: number;
|
|
350
|
+
readonly per: number;
|
|
351
|
+
readonly total: number;
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
export interface ConversationOptions {
|
|
355
|
+
readonly includeAll?: boolean;
|
|
356
|
+
readonly connectors?: string[];
|
|
357
|
+
readonly fromDate?: string;
|
|
358
|
+
readonly toDate?: string;
|
|
359
|
+
readonly fields?: string[];
|
|
360
|
+
readonly maxPersonas?: number | undefined;
|
|
361
|
+
readonly maxActsPerPersona?: number | undefined;
|
|
362
|
+
}
|
|
363
|
+
export interface ProcessedAct {
|
|
364
|
+
readonly datetime: string;
|
|
365
|
+
readonly type: string;
|
|
366
|
+
readonly message: string;
|
|
367
|
+
readonly contact_information?: string | null;
|
|
368
|
+
readonly flow_idn?: string;
|
|
369
|
+
readonly skill_idn?: string;
|
|
370
|
+
readonly session_id?: string;
|
|
371
|
+
}
|
|
372
|
+
export interface ProcessedPersona {
|
|
373
|
+
readonly id: string;
|
|
374
|
+
readonly name: string;
|
|
375
|
+
readonly phone: string | null;
|
|
376
|
+
readonly act_count: number;
|
|
377
|
+
readonly acts: readonly ProcessedAct[];
|
|
378
|
+
}
|
|
379
|
+
export interface ConversationsData {
|
|
380
|
+
readonly personas: readonly ProcessedPersona[];
|
|
381
|
+
readonly total_personas: number;
|
|
382
|
+
readonly total_acts: number;
|
|
383
|
+
readonly generated_at: string;
|
|
384
|
+
}
|
|
284
385
|
//# sourceMappingURL=types.d.ts.map
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "newo",
|
|
3
|
-
"version": "1.
|
|
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.1",
|
|
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,11 @@ import type {
|
|
|
10
10
|
AkbImportArticle,
|
|
11
11
|
CustomerProfile,
|
|
12
12
|
CustomerAttribute,
|
|
13
|
-
CustomerAttributesResponse
|
|
13
|
+
CustomerAttributesResponse,
|
|
14
|
+
UserPersonaResponse,
|
|
15
|
+
UserPersona,
|
|
16
|
+
ChatHistoryParams,
|
|
17
|
+
ChatHistoryResponse
|
|
14
18
|
} from './types.js';
|
|
15
19
|
|
|
16
20
|
// Per-request retry tracking to avoid shared state issues
|
|
@@ -152,4 +156,42 @@ export async function updateCustomerAttribute(client: AxiosInstance, attribute:
|
|
|
152
156
|
possible_values: attribute.possible_values,
|
|
153
157
|
value_type: attribute.value_type
|
|
154
158
|
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Conversation API Functions
|
|
162
|
+
|
|
163
|
+
export async function listUserPersonas(client: AxiosInstance, page: number = 1, per: number = 50): Promise<UserPersonaResponse> {
|
|
164
|
+
const response = await client.get<UserPersonaResponse>('/api/v1/bff/conversations/user-personas', {
|
|
165
|
+
params: { page, per }
|
|
166
|
+
});
|
|
167
|
+
return response.data;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export async function getUserPersona(client: AxiosInstance, personaId: string): Promise<UserPersona> {
|
|
171
|
+
const response = await client.get<UserPersona>(`/api/v1/bff/conversations/user-personas/${personaId}`);
|
|
172
|
+
return response.data;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export async function getAccount(client: AxiosInstance): Promise<{ id: string; [key: string]: any }> {
|
|
176
|
+
const response = await client.get<{ id: string; [key: string]: any }>('/api/v1/account');
|
|
177
|
+
return response.data;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
export async function getChatHistory(client: AxiosInstance, params: ChatHistoryParams): Promise<ChatHistoryResponse> {
|
|
182
|
+
const queryParams: Record<string, any> = {
|
|
183
|
+
user_actor_id: params.user_actor_id,
|
|
184
|
+
page: params.page || 1,
|
|
185
|
+
per: params.per || 50
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
// Only add agent_actor_id if provided
|
|
189
|
+
if (params.agent_actor_id) {
|
|
190
|
+
queryParams.agent_actor_id = params.agent_actor_id;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const response = await client.get<ChatHistoryResponse>('/api/v1/chat/history', {
|
|
194
|
+
params: queryParams
|
|
195
|
+
});
|
|
196
|
+
return response.data;
|
|
155
197
|
}
|
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,9 @@ import {
|
|
|
7
7
|
listFlowStates,
|
|
8
8
|
getProjectMeta,
|
|
9
9
|
getCustomerAttributes,
|
|
10
|
-
updateCustomerAttribute
|
|
10
|
+
updateCustomerAttribute,
|
|
11
|
+
listUserPersonas,
|
|
12
|
+
getChatHistory
|
|
11
13
|
} from './api.js';
|
|
12
14
|
import {
|
|
13
15
|
ensureState,
|
|
@@ -49,7 +51,13 @@ import type {
|
|
|
49
51
|
SkillMetadata,
|
|
50
52
|
FlowEvent,
|
|
51
53
|
FlowState,
|
|
52
|
-
CustomerAttribute
|
|
54
|
+
CustomerAttribute,
|
|
55
|
+
UserPersona,
|
|
56
|
+
ConversationAct,
|
|
57
|
+
ConversationOptions,
|
|
58
|
+
ConversationsData,
|
|
59
|
+
ProcessedPersona,
|
|
60
|
+
ProcessedAct
|
|
53
61
|
} from './types.js';
|
|
54
62
|
|
|
55
63
|
// Concurrency limits for API operations
|
|
@@ -1171,4 +1179,211 @@ async function generateFlowsYaml(
|
|
|
1171
1179
|
const yamlPath = flowsYamlPath(customer.idn);
|
|
1172
1180
|
await writeFileSafe(yamlPath, yamlContent);
|
|
1173
1181
|
console.log(`✓ Generated flows.yaml`);
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
// Conversation sync functions
|
|
1185
|
+
|
|
1186
|
+
export async function pullConversations(
|
|
1187
|
+
client: AxiosInstance,
|
|
1188
|
+
customer: CustomerConfig,
|
|
1189
|
+
options: ConversationOptions = {},
|
|
1190
|
+
verbose: boolean = false
|
|
1191
|
+
): Promise<void> {
|
|
1192
|
+
if (verbose) console.log(`💬 Fetching conversations for customer ${customer.idn}...`);
|
|
1193
|
+
|
|
1194
|
+
try {
|
|
1195
|
+
// Get all user personas with pagination
|
|
1196
|
+
const allPersonas: UserPersona[] = [];
|
|
1197
|
+
let page = 1;
|
|
1198
|
+
const perPage = 50;
|
|
1199
|
+
let hasMore = true;
|
|
1200
|
+
|
|
1201
|
+
while (hasMore) {
|
|
1202
|
+
const response = await listUserPersonas(client, page, perPage);
|
|
1203
|
+
allPersonas.push(...response.items);
|
|
1204
|
+
|
|
1205
|
+
if (verbose) console.log(`📋 Page ${page}: Found ${response.items.length} personas (${allPersonas.length}/${response.metadata.total} total)`);
|
|
1206
|
+
|
|
1207
|
+
hasMore = response.items.length === perPage && allPersonas.length < response.metadata.total;
|
|
1208
|
+
page++;
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
if (options.maxPersonas && allPersonas.length > options.maxPersonas) {
|
|
1212
|
+
allPersonas.splice(options.maxPersonas);
|
|
1213
|
+
if (verbose) console.log(`⚠️ Limited to ${options.maxPersonas} personas as requested`);
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
if (verbose) console.log(`👥 Processing ${allPersonas.length} personas...`);
|
|
1217
|
+
|
|
1218
|
+
// Process personas concurrently with limited concurrency
|
|
1219
|
+
const processedPersonas: ProcessedPersona[] = [];
|
|
1220
|
+
|
|
1221
|
+
await Promise.all(allPersonas.map(persona => concurrencyLimit(async () => {
|
|
1222
|
+
try {
|
|
1223
|
+
// Extract phone number from actors
|
|
1224
|
+
const phoneActor = persona.actors.find(actor =>
|
|
1225
|
+
actor.integration_idn === 'newo_voice' &&
|
|
1226
|
+
actor.connector_idn === 'newo_voice_connector' &&
|
|
1227
|
+
actor.contact_information?.startsWith('+')
|
|
1228
|
+
);
|
|
1229
|
+
const phone = phoneActor?.contact_information || null;
|
|
1230
|
+
|
|
1231
|
+
// Get acts for this persona
|
|
1232
|
+
const allActs: ConversationAct[] = [];
|
|
1233
|
+
let actPage = 1;
|
|
1234
|
+
const actsPerPage = 100; // Higher limit for acts
|
|
1235
|
+
let hasMoreActs = true;
|
|
1236
|
+
|
|
1237
|
+
while (hasMoreActs) {
|
|
1238
|
+
// First try the chat history API as alternative
|
|
1239
|
+
try {
|
|
1240
|
+
// Get user actor IDs from persona actors
|
|
1241
|
+
const userActors = persona.actors.filter(actor =>
|
|
1242
|
+
actor.integration_idn === 'newo_voice' &&
|
|
1243
|
+
actor.connector_idn === 'newo_voice_connector'
|
|
1244
|
+
);
|
|
1245
|
+
|
|
1246
|
+
if (userActors.length > 0) {
|
|
1247
|
+
const chatHistoryParams = {
|
|
1248
|
+
user_actor_id: userActors[0]!.id,
|
|
1249
|
+
page: actPage,
|
|
1250
|
+
per: actsPerPage
|
|
1251
|
+
};
|
|
1252
|
+
|
|
1253
|
+
const chatResponse = await getChatHistory(client, chatHistoryParams);
|
|
1254
|
+
if (chatResponse.items && chatResponse.items.length > 0) {
|
|
1255
|
+
// Convert chat history format to acts format - create minimal ConversationAct objects
|
|
1256
|
+
const convertedActs: ConversationAct[] = chatResponse.items.map((item: any) => ({
|
|
1257
|
+
id: item.id || `chat_${Math.random()}`,
|
|
1258
|
+
command_act_id: null,
|
|
1259
|
+
external_event_id: item.external_event_id || 'chat_history',
|
|
1260
|
+
arguments: [],
|
|
1261
|
+
reference_idn: (item.is_agent === true) ? 'agent_message' : 'user_message',
|
|
1262
|
+
runtime_context_id: item.runtime_context_id || 'chat_history',
|
|
1263
|
+
source_text: item.payload?.text || item.message || item.content || item.text || '',
|
|
1264
|
+
original_text: item.payload?.text || item.message || item.content || item.text || '',
|
|
1265
|
+
datetime: item.datetime || item.created_at || item.timestamp || new Date().toISOString(),
|
|
1266
|
+
user_actor_id: userActors[0]!.id,
|
|
1267
|
+
agent_actor_id: null,
|
|
1268
|
+
user_persona_id: persona.id,
|
|
1269
|
+
user_persona_name: persona.name,
|
|
1270
|
+
agent_persona_id: item.agent_persona_id || 'unknown',
|
|
1271
|
+
external_id: item.external_id || null,
|
|
1272
|
+
integration_idn: 'newo_voice',
|
|
1273
|
+
connector_idn: 'newo_voice_connector',
|
|
1274
|
+
to_integration_idn: null,
|
|
1275
|
+
to_connector_idn: null,
|
|
1276
|
+
is_agent: Boolean(item.is_agent === true),
|
|
1277
|
+
project_idn: null,
|
|
1278
|
+
flow_idn: item.flow_idn || 'unknown',
|
|
1279
|
+
skill_idn: item.skill_idn || 'unknown',
|
|
1280
|
+
session_id: item.session_id || 'unknown',
|
|
1281
|
+
recordings: item.recordings || [],
|
|
1282
|
+
contact_information: item.contact_information || null
|
|
1283
|
+
}));
|
|
1284
|
+
|
|
1285
|
+
allActs.push(...convertedActs);
|
|
1286
|
+
|
|
1287
|
+
if (verbose && convertedActs.length > 0) {
|
|
1288
|
+
console.log(` 👤 ${persona.name}: Chat History - ${convertedActs.length} messages (${allActs.length} total)`);
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
hasMoreActs = chatResponse.items.length === actsPerPage &&
|
|
1292
|
+
chatResponse.metadata?.total !== undefined &&
|
|
1293
|
+
allActs.length < chatResponse.metadata.total;
|
|
1294
|
+
actPage++;
|
|
1295
|
+
continue; // Skip the original acts API call
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
} catch (chatError) {
|
|
1299
|
+
if (verbose) console.log(` ⚠️ Chat history failed for ${persona.name}: ${chatError instanceof Error ? chatError.message : String(chatError)}`);
|
|
1300
|
+
// No fallback - only use chat history API
|
|
1301
|
+
hasMoreActs = false;
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
// Sort acts by datetime ascending (chronological order)
|
|
1306
|
+
allActs.sort((a, b) => new Date(a.datetime).getTime() - new Date(b.datetime).getTime());
|
|
1307
|
+
|
|
1308
|
+
// Process acts into simplified format - exclude redundant fields
|
|
1309
|
+
const processedActs: ProcessedAct[] = allActs.map(act => {
|
|
1310
|
+
const processedAct: ProcessedAct = {
|
|
1311
|
+
datetime: act.datetime,
|
|
1312
|
+
type: act.reference_idn,
|
|
1313
|
+
message: act.source_text
|
|
1314
|
+
};
|
|
1315
|
+
|
|
1316
|
+
// Only include non-redundant fields
|
|
1317
|
+
if (act.contact_information) {
|
|
1318
|
+
(processedAct as any).contact_information = act.contact_information;
|
|
1319
|
+
}
|
|
1320
|
+
if (act.flow_idn && act.flow_idn !== 'unknown') {
|
|
1321
|
+
(processedAct as any).flow_idn = act.flow_idn;
|
|
1322
|
+
}
|
|
1323
|
+
if (act.skill_idn && act.skill_idn !== 'unknown') {
|
|
1324
|
+
(processedAct as any).skill_idn = act.skill_idn;
|
|
1325
|
+
}
|
|
1326
|
+
if (act.session_id && act.session_id !== 'unknown') {
|
|
1327
|
+
(processedAct as any).session_id = act.session_id;
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
return processedAct;
|
|
1331
|
+
});
|
|
1332
|
+
|
|
1333
|
+
processedPersonas.push({
|
|
1334
|
+
id: persona.id,
|
|
1335
|
+
name: persona.name,
|
|
1336
|
+
phone,
|
|
1337
|
+
act_count: persona.act_count,
|
|
1338
|
+
acts: processedActs
|
|
1339
|
+
});
|
|
1340
|
+
|
|
1341
|
+
if (verbose) console.log(` ✓ Processed ${persona.name}: ${processedActs.length} acts`);
|
|
1342
|
+
} catch (error) {
|
|
1343
|
+
console.error(`❌ Failed to process persona ${persona.name}:`, error);
|
|
1344
|
+
// Continue with other personas
|
|
1345
|
+
}
|
|
1346
|
+
})));
|
|
1347
|
+
|
|
1348
|
+
// Sort personas by most recent act time (descending) - use latest act from acts array
|
|
1349
|
+
processedPersonas.sort((a, b) => {
|
|
1350
|
+
const aLatestTime = a.acts.length > 0 ? a.acts[a.acts.length - 1]!.datetime : '1970-01-01T00:00:00.000Z';
|
|
1351
|
+
const bLatestTime = b.acts.length > 0 ? b.acts[b.acts.length - 1]!.datetime : '1970-01-01T00:00:00.000Z';
|
|
1352
|
+
return new Date(bLatestTime).getTime() - new Date(aLatestTime).getTime();
|
|
1353
|
+
});
|
|
1354
|
+
|
|
1355
|
+
// Calculate totals
|
|
1356
|
+
const totalActs = processedPersonas.reduce((sum, persona) => sum + persona.acts.length, 0);
|
|
1357
|
+
|
|
1358
|
+
// Create final conversations data
|
|
1359
|
+
const conversationsData: ConversationsData = {
|
|
1360
|
+
personas: processedPersonas,
|
|
1361
|
+
total_personas: processedPersonas.length,
|
|
1362
|
+
total_acts: totalActs,
|
|
1363
|
+
generated_at: new Date().toISOString()
|
|
1364
|
+
};
|
|
1365
|
+
|
|
1366
|
+
// Save to YAML file
|
|
1367
|
+
const conversationsPath = `newo_customers/${customer.idn}/conversations.yaml`;
|
|
1368
|
+
const yamlContent = yaml.dump(conversationsData, {
|
|
1369
|
+
indent: 2,
|
|
1370
|
+
quotingType: '"',
|
|
1371
|
+
forceQuotes: false,
|
|
1372
|
+
lineWidth: 120,
|
|
1373
|
+
noRefs: true,
|
|
1374
|
+
sortKeys: false,
|
|
1375
|
+
flowLevel: -1
|
|
1376
|
+
});
|
|
1377
|
+
|
|
1378
|
+
await writeFileSafe(conversationsPath, yamlContent);
|
|
1379
|
+
|
|
1380
|
+
if (verbose) {
|
|
1381
|
+
console.log(`✓ Saved conversations to ${conversationsPath}`);
|
|
1382
|
+
console.log(`📊 Summary: ${processedPersonas.length} personas, ${totalActs} total acts`);
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
} catch (error) {
|
|
1386
|
+
console.error(`❌ Failed to pull conversations for ${customer.idn}:`, error);
|
|
1387
|
+
throw error;
|
|
1388
|
+
}
|
|
1174
1389
|
}
|
package/src/types.ts
CHANGED
|
@@ -336,4 +336,118 @@ 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
|
+
|
|
403
|
+
export interface ChatHistoryParams {
|
|
404
|
+
readonly user_actor_id: string;
|
|
405
|
+
readonly agent_actor_id?: string;
|
|
406
|
+
readonly page?: number;
|
|
407
|
+
readonly per?: number;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
export interface ChatHistoryResponse {
|
|
411
|
+
readonly items: readonly any[]; // We'll define this after seeing the response structure
|
|
412
|
+
readonly metadata?: {
|
|
413
|
+
readonly page: number;
|
|
414
|
+
readonly per: number;
|
|
415
|
+
readonly total: number;
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
export interface ConversationOptions {
|
|
420
|
+
readonly includeAll?: boolean;
|
|
421
|
+
readonly connectors?: string[];
|
|
422
|
+
readonly fromDate?: string;
|
|
423
|
+
readonly toDate?: string;
|
|
424
|
+
readonly fields?: string[];
|
|
425
|
+
readonly maxPersonas?: number | undefined;
|
|
426
|
+
readonly maxActsPerPersona?: number | undefined;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// Processed conversation data for YAML output
|
|
430
|
+
export interface ProcessedAct {
|
|
431
|
+
readonly datetime: string;
|
|
432
|
+
readonly type: string;
|
|
433
|
+
readonly message: string;
|
|
434
|
+
readonly contact_information?: string | null;
|
|
435
|
+
readonly flow_idn?: string;
|
|
436
|
+
readonly skill_idn?: string;
|
|
437
|
+
readonly session_id?: string;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
export interface ProcessedPersona {
|
|
441
|
+
readonly id: string;
|
|
442
|
+
readonly name: string;
|
|
443
|
+
readonly phone: string | null;
|
|
444
|
+
readonly act_count: number;
|
|
445
|
+
readonly acts: readonly ProcessedAct[];
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
export interface ConversationsData {
|
|
449
|
+
readonly personas: readonly ProcessedPersona[];
|
|
450
|
+
readonly total_personas: number;
|
|
451
|
+
readonly total_acts: number;
|
|
452
|
+
readonly generated_at: string;
|
|
339
453
|
}
|