newo 3.7.1 → 3.7.3
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 +25 -1
- package/README.md +24 -0
- package/dist/api.d.ts +33 -1
- package/dist/api.js +22 -0
- package/dist/domain/strategies/sync/ProjectSyncStrategy.d.ts +7 -0
- package/dist/domain/strategies/sync/ProjectSyncStrategy.js +81 -1
- package/dist/domain/strategies/sync/V2ProjectSyncStrategy.d.ts +11 -0
- package/dist/domain/strategies/sync/V2ProjectSyncStrategy.js +125 -2
- package/dist/sync/flow-metadata.d.ts +67 -0
- package/dist/sync/flow-metadata.js +283 -0
- package/dist/sync/json-attr-utils.d.ts +40 -11
- package/dist/sync/json-attr-utils.js +89 -22
- package/dist/sync/push.js +65 -2
- package/dist/types.d.ts +37 -0
- package/package.json +1 -1
- package/src/api.ts +61 -0
- package/src/domain/strategies/sync/ProjectSyncStrategy.ts +100 -0
- package/src/domain/strategies/sync/V2ProjectSyncStrategy.ts +147 -0
- package/src/sync/flow-metadata.ts +345 -0
- package/src/sync/json-attr-utils.ts +84 -19
- package/src/sync/push.ts +75 -2
- package/src/types.ts +40 -0
package/src/api.ts
CHANGED
|
@@ -23,8 +23,11 @@ import type {
|
|
|
23
23
|
CreateSkillResponse,
|
|
24
24
|
CreateFlowEventRequest,
|
|
25
25
|
CreateFlowEventResponse,
|
|
26
|
+
UpdateFlowEventRequest,
|
|
26
27
|
CreateFlowStateRequest,
|
|
27
28
|
CreateFlowStateResponse,
|
|
29
|
+
UpdateFlowStateRequest,
|
|
30
|
+
UpdateFlowRequest,
|
|
28
31
|
CreateSkillParameterRequest,
|
|
29
32
|
CreateSkillParameterResponse,
|
|
30
33
|
CreateCustomerAttributeRequest,
|
|
@@ -151,6 +154,30 @@ export async function listFlowSkills(client: AxiosInstance, flowId: string): Pro
|
|
|
151
154
|
return response.data;
|
|
152
155
|
}
|
|
153
156
|
|
|
157
|
+
/**
|
|
158
|
+
* Fetch a single flow's top-level descriptor.
|
|
159
|
+
*
|
|
160
|
+
* Used by push to detect whether local metadata.yaml title/description
|
|
161
|
+
* differ from the platform before patching.
|
|
162
|
+
*/
|
|
163
|
+
export interface FlowDescriptor {
|
|
164
|
+
id: string;
|
|
165
|
+
idn: string;
|
|
166
|
+
title: string;
|
|
167
|
+
description: string | null;
|
|
168
|
+
agent_id: string;
|
|
169
|
+
default_runner_type: string;
|
|
170
|
+
default_model: { provider_idn: string; model_idn: string };
|
|
171
|
+
publication_datetime?: string;
|
|
172
|
+
last_change_datetime?: string;
|
|
173
|
+
creation_datetime?: string;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export async function getFlow(client: AxiosInstance, flowId: string): Promise<FlowDescriptor> {
|
|
177
|
+
const response = await client.get<FlowDescriptor>(`/api/v1/designer/flows/${flowId}`);
|
|
178
|
+
return response.data;
|
|
179
|
+
}
|
|
180
|
+
|
|
154
181
|
export async function getSkill(client: AxiosInstance, skillId: string): Promise<Skill> {
|
|
155
182
|
const response = await client.get<Skill>(`/api/v1/designer/skills/${skillId}`);
|
|
156
183
|
return response.data;
|
|
@@ -345,6 +372,14 @@ export async function createFlowEvent(client: AxiosInstance, flowId: string, eve
|
|
|
345
372
|
return response.data;
|
|
346
373
|
}
|
|
347
374
|
|
|
375
|
+
export async function updateFlowEvent(
|
|
376
|
+
client: AxiosInstance,
|
|
377
|
+
eventId: string,
|
|
378
|
+
eventData: UpdateFlowEventRequest
|
|
379
|
+
): Promise<void> {
|
|
380
|
+
await client.patch(`/api/v1/designer/flows/events/${eventId}`, eventData);
|
|
381
|
+
}
|
|
382
|
+
|
|
348
383
|
export async function deleteFlowEvent(client: AxiosInstance, eventId: string): Promise<void> {
|
|
349
384
|
await client.delete(`/api/v1/designer/flows/events/${eventId}`);
|
|
350
385
|
}
|
|
@@ -354,6 +389,32 @@ export async function createFlowState(client: AxiosInstance, flowId: string, sta
|
|
|
354
389
|
return response.data;
|
|
355
390
|
}
|
|
356
391
|
|
|
392
|
+
export async function updateFlowState(
|
|
393
|
+
client: AxiosInstance,
|
|
394
|
+
stateId: string,
|
|
395
|
+
stateData: UpdateFlowStateRequest
|
|
396
|
+
): Promise<void> {
|
|
397
|
+
await client.put(`/api/v1/designer/flows/states/${stateId}`, stateData);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
export async function deleteFlowState(client: AxiosInstance, stateId: string): Promise<void> {
|
|
401
|
+
await client.delete(`/api/v1/designer/flows/states/${stateId}`);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Update flow metadata (title, description, runner type, default model).
|
|
406
|
+
*
|
|
407
|
+
* Uses PATCH /api/v1/designer/flows/{flowId}. The platform requires the
|
|
408
|
+
* full flow descriptor; sending a partial body returns 500.
|
|
409
|
+
*/
|
|
410
|
+
export async function updateFlow(
|
|
411
|
+
client: AxiosInstance,
|
|
412
|
+
flowId: string,
|
|
413
|
+
flowData: UpdateFlowRequest
|
|
414
|
+
): Promise<void> {
|
|
415
|
+
await client.patch(`/api/v1/designer/flows/${flowId}`, flowData);
|
|
416
|
+
}
|
|
417
|
+
|
|
357
418
|
export async function createSkillParameter(client: AxiosInstance, skillId: string, paramData: CreateSkillParameterRequest): Promise<CreateSkillParameterResponse> {
|
|
358
419
|
// Debug the parameter creation request
|
|
359
420
|
console.log('Creating parameter for skill:', skillId);
|
|
@@ -49,7 +49,14 @@ import {
|
|
|
49
49
|
publishFlow,
|
|
50
50
|
listLibraries,
|
|
51
51
|
updateLibrarySkill,
|
|
52
|
+
getFlow,
|
|
52
53
|
} from '../../../api.js';
|
|
54
|
+
import {
|
|
55
|
+
syncFlowMetadata,
|
|
56
|
+
emptyFlowSyncCounts,
|
|
57
|
+
totalFlowSyncOps,
|
|
58
|
+
describeFlowSyncCounts
|
|
59
|
+
} from '../../../sync/flow-metadata.js';
|
|
53
60
|
import {
|
|
54
61
|
ensureState,
|
|
55
62
|
writeFileSafe,
|
|
@@ -540,6 +547,24 @@ export class ProjectSyncStrategy implements ISyncStrategy<ProjectMeta, LocalProj
|
|
|
540
547
|
for (const change of changes) {
|
|
541
548
|
try {
|
|
542
549
|
if (change.operation === 'modified') {
|
|
550
|
+
// Flow-level metadata.yaml needs different handling than a skill
|
|
551
|
+
// script: we sync title/events/state_fields rather than uploading a
|
|
552
|
+
// file. Detected by filename. (GH issue #3)
|
|
553
|
+
if (change.path.endsWith('/metadata.yaml') && !change.path.includes('/libraries/')) {
|
|
554
|
+
const pathParts = change.path.split('/');
|
|
555
|
+
// {customer}/projects/{project}/{agent}/{flow}/metadata.yaml
|
|
556
|
+
// Last 5 segments end with metadata.yaml; skip if it's a skill
|
|
557
|
+
// metadata file (one extra segment) - skill metadata is handled
|
|
558
|
+
// by V1 legacy push, not by this strategy yet.
|
|
559
|
+
const tail = pathParts.slice(-5);
|
|
560
|
+
const isFlowMeta = tail[0] === 'projects' || tail[2] && tail[4] === 'metadata.yaml';
|
|
561
|
+
if (isFlowMeta && tail.length === 5) {
|
|
562
|
+
const updateResult = await this.pushFlowMetadataUpdate(client, change, mapData, newHashes);
|
|
563
|
+
result.updated += updateResult;
|
|
564
|
+
continue;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
|
|
543
568
|
const isLibrary = change.path.includes('/libraries/');
|
|
544
569
|
if (isLibrary) {
|
|
545
570
|
const updateResult = await this.pushLibrarySkillUpdate(client, change, mapData, newHashes);
|
|
@@ -568,6 +593,64 @@ export class ProjectSyncStrategy implements ISyncStrategy<ProjectMeta, LocalProj
|
|
|
568
593
|
return result;
|
|
569
594
|
}
|
|
570
595
|
|
|
596
|
+
/**
|
|
597
|
+
* Push a flow metadata.yaml change — syncs title, events, and state_fields
|
|
598
|
+
* to the platform. Closes GH issue #3 (events/title silently un-synced).
|
|
599
|
+
*
|
|
600
|
+
* Path shape: newo_customers/{customer}/projects/{project}/{agent}/{flow}/metadata.yaml
|
|
601
|
+
*/
|
|
602
|
+
private async pushFlowMetadataUpdate(
|
|
603
|
+
client: AxiosInstance,
|
|
604
|
+
change: ChangeItem<LocalProjectData>,
|
|
605
|
+
mapData: ProjectMap,
|
|
606
|
+
newHashes: HashStore
|
|
607
|
+
): Promise<number> {
|
|
608
|
+
const pathParts = change.path.split('/');
|
|
609
|
+
// Tail: projects/{project}/{agent}/{flow}/metadata.yaml
|
|
610
|
+
const flowIdn = pathParts[pathParts.length - 2] || '';
|
|
611
|
+
const agentIdn = pathParts[pathParts.length - 3] || '';
|
|
612
|
+
const projectIdn = pathParts[pathParts.length - 4] || '';
|
|
613
|
+
|
|
614
|
+
const projectData = mapData.projects[projectIdn];
|
|
615
|
+
const agentData = projectData?.agents[agentIdn];
|
|
616
|
+
const flowData = agentData?.flows[flowIdn];
|
|
617
|
+
|
|
618
|
+
if (!flowData?.id) {
|
|
619
|
+
this.logger.warn(`Flow metadata change but flow not in project map: ${projectIdn}/${agentIdn}/${flowIdn}`);
|
|
620
|
+
return 0;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
const content = await fs.readFile(change.path, 'utf8');
|
|
624
|
+
let localFlow: FlowMetadata;
|
|
625
|
+
try {
|
|
626
|
+
localFlow = yaml.load(content) as FlowMetadata;
|
|
627
|
+
} catch (error) {
|
|
628
|
+
throw new Error(`Failed to parse ${change.path}: ${error instanceof Error ? error.message : String(error)}`);
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
let remoteFlow = null;
|
|
632
|
+
try {
|
|
633
|
+
remoteFlow = await getFlow(client, flowData.id);
|
|
634
|
+
} catch (error: any) {
|
|
635
|
+
this.logger.verbose(`Could not GET flow ${flowIdn}: ${error.response?.status ?? error.message}`);
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
const counts = emptyFlowSyncCounts();
|
|
639
|
+
await syncFlowMetadata(client, flowData.id, localFlow, remoteFlow, false, counts);
|
|
640
|
+
|
|
641
|
+
const total = totalFlowSyncOps(counts);
|
|
642
|
+
if (total > 0) {
|
|
643
|
+
this.logger.info(`↑ Flow ${flowIdn}: ${describeFlowSyncCounts(counts)}`);
|
|
644
|
+
}
|
|
645
|
+
for (const err of counts.errors) {
|
|
646
|
+
this.logger.warn(err);
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
// Stamp the hash so the next push skips this file unless it changes again.
|
|
650
|
+
newHashes[change.path] = sha256(content);
|
|
651
|
+
return total;
|
|
652
|
+
}
|
|
653
|
+
|
|
571
654
|
/**
|
|
572
655
|
* Push a skill update
|
|
573
656
|
*/
|
|
@@ -709,6 +792,23 @@ export class ProjectSyncStrategy implements ISyncStrategy<ProjectMeta, LocalProj
|
|
|
709
792
|
for (const [projectIdn, projectData] of Object.entries(mapData.projects)) {
|
|
710
793
|
for (const [agentIdn, agentData] of Object.entries(projectData.agents)) {
|
|
711
794
|
for (const [flowIdn, flowData] of Object.entries(agentData.flows)) {
|
|
795
|
+
// Flow metadata change detection (title/events/state_fields).
|
|
796
|
+
// Surfaced as a change so push() picks it up alongside skill edits.
|
|
797
|
+
const flowMetaPath = flowMetadataPath(customer.idn, projectIdn, agentIdn, flowIdn);
|
|
798
|
+
if (await fs.pathExists(flowMetaPath)) {
|
|
799
|
+
const content = await fs.readFile(flowMetaPath, 'utf8');
|
|
800
|
+
const currentHash = sha256(content);
|
|
801
|
+
const storedHash = hashes[flowMetaPath];
|
|
802
|
+
|
|
803
|
+
if (storedHash !== currentHash) {
|
|
804
|
+
changes.push({
|
|
805
|
+
item: {} as LocalProjectData,
|
|
806
|
+
operation: 'modified',
|
|
807
|
+
path: flowMetaPath
|
|
808
|
+
});
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
|
|
712
812
|
for (const [skillIdn, _skillData] of Object.entries(flowData.skills)) {
|
|
713
813
|
const skillFile = await getSingleSkillFile(customer.idn, projectIdn, agentIdn, flowIdn, skillIdn);
|
|
714
814
|
|
|
@@ -33,6 +33,7 @@ import type {
|
|
|
33
33
|
Skill,
|
|
34
34
|
FlowEvent,
|
|
35
35
|
FlowState,
|
|
36
|
+
FlowMetadata,
|
|
36
37
|
ProjectData,
|
|
37
38
|
ProjectMap,
|
|
38
39
|
SkillMetadata
|
|
@@ -51,7 +52,14 @@ import {
|
|
|
51
52
|
getCustomerAttributes,
|
|
52
53
|
listLibraries,
|
|
53
54
|
updateLibrarySkill,
|
|
55
|
+
getFlow,
|
|
54
56
|
} from '../../../api.js';
|
|
57
|
+
import {
|
|
58
|
+
syncFlowMetadata,
|
|
59
|
+
emptyFlowSyncCounts,
|
|
60
|
+
totalFlowSyncOps,
|
|
61
|
+
describeFlowSyncCounts
|
|
62
|
+
} from '../../../sync/flow-metadata.js';
|
|
55
63
|
import type { LibraryResponse } from '../../../api.js';
|
|
56
64
|
import {
|
|
57
65
|
ensureStateOnly,
|
|
@@ -84,7 +92,10 @@ import {
|
|
|
84
92
|
buildV2InlineSkill,
|
|
85
93
|
buildV2FlowEvent,
|
|
86
94
|
buildV2StateField,
|
|
95
|
+
parseV2FlowYaml,
|
|
87
96
|
type V2InlineSkill,
|
|
97
|
+
type V2FlowEvent,
|
|
98
|
+
type V2StateField,
|
|
88
99
|
} from '../../../format/v2-yaml.js';
|
|
89
100
|
import { isContentDifferent } from '../../../sync/skill-files.js';
|
|
90
101
|
import yaml from 'js-yaml';
|
|
@@ -648,6 +659,15 @@ export class V2ProjectSyncStrategy implements ISyncStrategy<ProjectMeta, LocalPr
|
|
|
648
659
|
for (const change of changes) {
|
|
649
660
|
try {
|
|
650
661
|
if (change.operation === 'modified') {
|
|
662
|
+
// V2 flow YAML: newo_customers/{cust}/{proj}/agents/{agent}/flows/{flow}/{flow}.yaml
|
|
663
|
+
// The flow YAML carries title, events, and state_fields inline, so
|
|
664
|
+
// changes there must sync to the platform like V1 metadata.yaml.
|
|
665
|
+
if (this.isV2FlowYamlPath(change.path)) {
|
|
666
|
+
const count = await this.pushV2FlowYamlUpdate(client, change, mapData, newHashes);
|
|
667
|
+
result.updated += count;
|
|
668
|
+
continue;
|
|
669
|
+
}
|
|
670
|
+
|
|
651
671
|
// Detect if this is a library skill or flow skill by path
|
|
652
672
|
const isLibrary = change.path.includes('/libraries/');
|
|
653
673
|
const count = isLibrary
|
|
@@ -671,6 +691,115 @@ export class V2ProjectSyncStrategy implements ISyncStrategy<ProjectMeta, LocalPr
|
|
|
671
691
|
return result;
|
|
672
692
|
}
|
|
673
693
|
|
|
694
|
+
/**
|
|
695
|
+
* Recognize the V2 flow YAML location: .../agents/{agent}/flows/{flow}/{flow}.yaml
|
|
696
|
+
* Distinguishes it from skill scripts, library YAMLs, and attribute files.
|
|
697
|
+
*/
|
|
698
|
+
private isV2FlowYamlPath(p: string): boolean {
|
|
699
|
+
const parts = p.split('/');
|
|
700
|
+
const file = parts[parts.length - 1];
|
|
701
|
+
if (!file || !file.endsWith('.yaml')) return false;
|
|
702
|
+
// .../agents/{agent}/flows/{flow}/{flow}.yaml → last 5 parts:
|
|
703
|
+
// agents, {agent}, flows, {flow}, {flow}.yaml
|
|
704
|
+
if (parts.length < 5) return false;
|
|
705
|
+
const flowsKeyword = parts[parts.length - 3];
|
|
706
|
+
const agentsKeyword = parts[parts.length - 5];
|
|
707
|
+
const flowFolder = parts[parts.length - 2] || '';
|
|
708
|
+
const stem = file.slice(0, -'.yaml'.length);
|
|
709
|
+
return flowsKeyword === 'flows' && agentsKeyword === 'agents' && stem === flowFolder;
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
/**
|
|
713
|
+
* Push V2 flow YAML changes. Closes GH issue #3 for newo_v2 layout.
|
|
714
|
+
* Parses the V2 YAML, converts to V1-shaped FlowMetadata, and reuses the
|
|
715
|
+
* shared syncFlowMetadata routine that calls PATCH/POST/DELETE per child.
|
|
716
|
+
*/
|
|
717
|
+
private async pushV2FlowYamlUpdate(
|
|
718
|
+
client: AxiosInstance,
|
|
719
|
+
change: ChangeItem<LocalProjectData>,
|
|
720
|
+
mapData: ProjectMap,
|
|
721
|
+
newHashes: HashStore
|
|
722
|
+
): Promise<number> {
|
|
723
|
+
const parts = change.path.split('/');
|
|
724
|
+
const flowIdn = parts[parts.length - 2] || '';
|
|
725
|
+
const agentIdn = parts[parts.length - 4] || '';
|
|
726
|
+
const projectIdn = parts[parts.length - 6] || '';
|
|
727
|
+
|
|
728
|
+
const projectData = mapData.projects[projectIdn];
|
|
729
|
+
const agentData = projectData?.agents[agentIdn];
|
|
730
|
+
const flowData = agentData?.flows[flowIdn];
|
|
731
|
+
|
|
732
|
+
if (!flowData?.id) {
|
|
733
|
+
this.logger.warn(`[newo_v2] Flow YAML change but flow not in project map: ${projectIdn}/${agentIdn}/${flowIdn}`);
|
|
734
|
+
return 0;
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
const v2Flow = await parseV2FlowYaml(change.path);
|
|
738
|
+
|
|
739
|
+
// Convert V2 → V1-shaped FlowMetadata for the shared sync routine.
|
|
740
|
+
// V2 events lack `id` and `description`; we fill defaults so the shape
|
|
741
|
+
// matches FlowEvent[] / FlowState[] expected by syncFlowMetadata.
|
|
742
|
+
// Optional fields are omitted (not set to undefined) to satisfy
|
|
743
|
+
// exactOptionalPropertyTypes.
|
|
744
|
+
const localMeta: FlowMetadata = {
|
|
745
|
+
id: flowData.id,
|
|
746
|
+
idn: v2Flow.idn,
|
|
747
|
+
title: v2Flow.title,
|
|
748
|
+
description: v2Flow.description ?? '',
|
|
749
|
+
default_runner_type: (v2Flow.default_runner_type as 'guidance' | 'nsl') || 'guidance',
|
|
750
|
+
default_model: {
|
|
751
|
+
provider_idn: v2Flow.default_provider_idn,
|
|
752
|
+
model_idn: v2Flow.default_model_idn,
|
|
753
|
+
},
|
|
754
|
+
events: (v2Flow.events || []).map((e: V2FlowEvent) => {
|
|
755
|
+
const out: FlowEvent = {
|
|
756
|
+
id: '',
|
|
757
|
+
idn: e.idn,
|
|
758
|
+
description: '',
|
|
759
|
+
skill_selector: e.skill_selector as 'first' | 'last' | 'random' | 'all',
|
|
760
|
+
interrupt_mode: (e.interrupt_mode || 'queue') as 'allow' | 'deny' | 'queue',
|
|
761
|
+
...(e.skill_idn != null ? { skill_idn: e.skill_idn } : {}),
|
|
762
|
+
...(e.state_idn != null ? { state_idn: e.state_idn } : {}),
|
|
763
|
+
...(e.integration_idn != null ? { integration_idn: e.integration_idn } : {}),
|
|
764
|
+
...(e.connector_idn != null ? { connector_idn: e.connector_idn } : {}),
|
|
765
|
+
};
|
|
766
|
+
return out;
|
|
767
|
+
}),
|
|
768
|
+
state_fields: (v2Flow.state_fields || []).map((s: V2StateField) => {
|
|
769
|
+
const out: FlowState = {
|
|
770
|
+
id: '',
|
|
771
|
+
idn: s.idn,
|
|
772
|
+
title: s.title || s.idn,
|
|
773
|
+
scope: (s.scope || 'flow') as 'flow' | 'agent' | 'project' | 'global',
|
|
774
|
+
...(s.default_value != null ? { default_value: s.default_value } : {}),
|
|
775
|
+
};
|
|
776
|
+
return out;
|
|
777
|
+
}),
|
|
778
|
+
};
|
|
779
|
+
|
|
780
|
+
let remoteFlow = null;
|
|
781
|
+
try {
|
|
782
|
+
remoteFlow = await getFlow(client, flowData.id);
|
|
783
|
+
} catch (error: any) {
|
|
784
|
+
this.logger.verbose(`[newo_v2] Could not GET flow ${flowIdn}: ${error.response?.status ?? error.message}`);
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
const counts = emptyFlowSyncCounts();
|
|
788
|
+
await syncFlowMetadata(client, flowData.id, localMeta, remoteFlow, false, counts);
|
|
789
|
+
|
|
790
|
+
const total = totalFlowSyncOps(counts);
|
|
791
|
+
if (total > 0) {
|
|
792
|
+
this.logger.info(`[newo_v2] ↑ Flow ${flowIdn}: ${describeFlowSyncCounts(counts)}`);
|
|
793
|
+
}
|
|
794
|
+
for (const err of counts.errors) {
|
|
795
|
+
this.logger.warn(err);
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
const content = await fs.readFile(change.path, 'utf8');
|
|
799
|
+
newHashes[change.path] = sha256(content);
|
|
800
|
+
return total;
|
|
801
|
+
}
|
|
802
|
+
|
|
674
803
|
/**
|
|
675
804
|
* Push a V2 skill update
|
|
676
805
|
*
|
|
@@ -801,6 +930,24 @@ export class V2ProjectSyncStrategy implements ISyncStrategy<ProjectMeta, LocalPr
|
|
|
801
930
|
for (const [projectIdn, projectData] of Object.entries(mapData.projects)) {
|
|
802
931
|
// Flow skills
|
|
803
932
|
for (const [agentIdn, agentData] of Object.entries(projectData.agents)) {
|
|
933
|
+
for (const [flowIdn, _flowData] of Object.entries(agentData.flows)) {
|
|
934
|
+
// V2 stores flow events / state_fields / title inline in the flow
|
|
935
|
+
// YAML. Detect changes here so push() can sync them (GH issue #3).
|
|
936
|
+
const flowYamlPath = v2FlowYamlPath(customer.idn, projectIdn, agentIdn, flowIdn);
|
|
937
|
+
if (await fs.pathExists(flowYamlPath)) {
|
|
938
|
+
const content = await fs.readFile(flowYamlPath, 'utf8');
|
|
939
|
+
const currentHash = sha256(content);
|
|
940
|
+
const storedHash = hashes[flowYamlPath];
|
|
941
|
+
if (storedHash !== currentHash) {
|
|
942
|
+
changes.push({
|
|
943
|
+
item: {} as LocalProjectData,
|
|
944
|
+
operation: 'modified',
|
|
945
|
+
path: flowYamlPath
|
|
946
|
+
});
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
|
|
804
951
|
for (const [flowIdn, flowData] of Object.entries(agentData.flows)) {
|
|
805
952
|
for (const [skillIdn, skillMeta] of Object.entries(flowData.skills)) {
|
|
806
953
|
const scriptPath = v2SkillScriptPath(
|